Develop with pleasure!

福岡でCloudとかBlockchainとか。

BitcoinへCovenantsの導入を提案するbip-coshv

Bitcoinは通常ロックスクリプトにロックされたコインをアンロックできるアンロックスクリプトを提供できれば、そのコインはどこにでも送金できる。これに対し、あるコインをアンロックした場合、そのコインの送付先を限定する仕組みがCovenantsだ。つまりある特定のスクリプトにロックされたコインは、ある特定のロックスクリプトにしか送信できなくするというもの。このようにコインの送信先を制限することで、例えば、秘密鍵が盗難されても、その秘密鍵の管理下にあるコインは特定の宛先にしか遅れない金庫のような仕組みを作ることができる。

このCovenantsのコンセプトは元々、2016年にミラノで開催されたScaling BitcoinでEmin Gun Sirerが発表したものだ。Eminが提案したのは、CheckOutputVerifyというopcodeを追加してCovenantsを構成するというもの。CheckOutputVerifyは、アウトプットのインデックス、そのアウトプットのコインの量、そのアウトプットscriptPubkeyの3つを引数にとり、そのロックスクリプトを使用しようとしているトランザクションの指定されたインデックスのアウトプットに、指定された量、指定されたscriptPubkeyがセットされているか検証するというアプローチ。詳細は以前書いた↓参照。

techmedia-think.hatenablog.com

これに対し、当時Blockstreamが開発していたElementsでサポートしている任意メッセージの署名検証ができるopcodeOP_CHECKSIGFROMSTACKを使ってCovenantsを実現するという提案もあった。CheckOutputVerifyに比べて直観的なロックではないものの確かにこのアプローチでもコインの送金先を制御できる。(BCHに導入された同様の機能であるOP_CHECKDATASIGを使えばBCHではこの方法は既に利用可能である)詳細は以前書いた↓参照。ただ、これはスクリプトを組むのがとても大変で複雑になるので、オススメはしない。

techmedia-think.hatenablog.com

今回のbip-coshvの提案者であるJeremy Rubinも、2017年のStanford Blockchain Conferenceや2017年2月に日本で開催されたDGLab主催のBC²でCovenantsのさまざまなユースケースや拡張について発表している↓

https://bc-2.jp/archive/season1/materials/0303-JP-StructuringMultiTransactionContracts.pdf

そして、今回Jeremy Rubinによって書かれたCovenantsのBIPドラフトが↓

https://github.com/JeremyRubin/bips/blob/op-checkoutputshashverify/bip-coshv.mediawiki

今回の提案では、CHECKOUTPUTSHASHVERIFYという新しいopcodeをTapscriptのOP_SUCCESS系opcodeの1つに割り当てて導入する。このopcodeを使ったCovenantsの仕組みはEminのCheckOutputVerifyのアプローチと似ている。ただCheckOutputVerifyと違うのは、 CHECKOUTPUTSHASHVERIFYが取る引数は1つのみで、この引数の値はCHECKOUTPUTSHASHVERIFYを含むUTXOを使用する際のトランザクションの全アウトプットをdouble-SHA256したデータである。

... OP_CHECKOUTPUTSHASHVERIFY <トランザクションの全アウトプットのdouble-SHA256ハッシュ> ...

トランザクションアウトプットのデータはコインの量とscripPubkeyで構成されているため、CHECKOUTPUTSHASHVERIFYはコインの送金先(トランザクションアウトプット)の情報を制御することになる。もちろんCHECKOUTPUTSHASHVERIFYはTapscript内で自由に利用できるので、OP_IF分岐を利用して送信先の条件を複数コントロールできる。

そして実装を簡単にするため、まずこの段階でCHECKOUTPUTSHASHVERIFYを使う場合、トランザクションのインプットは1つのみと制限される。また全アウトプットへの事前コミットが必要というのも制約になる。なので、トランザクション手数料なんかも加味した上で送信先トランザクションアウトプットを予め決めておかなければならない。

またBIPにはCovenantsを利用した金庫のユースケース以外にも、ブロックの混雑具合に応じてトランザクションの送金をCHECKOUTPUTSHASHVERIFYを使って束ねる方法が紹介されている。トランザクションを多数の送信先に送らないといけない場合、混雑時にそういったトランザクションを作成すると手数料高になるが、とりあえずCHECKOUTPUTSHASHVERIFYでロックされたアウトプットに送金しておき、混雑が解消されたタイミングでCHECKOUTPUTSHASHVERIFYのアウトプットを多数の送信先に送信するというユースケースで、この場合最初のCHECKOUTPUTSHASHVERIFYで送金先自体は保証されているというのがポイントになる。他にもCoinJoinやChannel Factoryへの適用などのユースケースが挙げられている。

もともと、Covenantsはfungibilityを損ねる可能性があるということで具体的な実装の提案はなかったが、今回Taprooの機能と一緒に組み込むことが、そのリスクの低減になりそうだ。この場合、Taprootのキーパスを利用して、そのような条件がセットされていることを秘匿した状態で条件に合意した送金を可能にするが、その担保は実はCHECKOUTPUTSHASHVERIFYで行われているということが可能になる。

果たして、遂にCovenantsの導入になるか!?

詳細な仕様は、以下BIPドラフトの内容参照↓

Abstract

このBIPはTapscriptバージョン 0に対してアクティブ化される新しいopcode OP_CHECKOUTPUTSHASHVERIFYを提案する。

新しいopcodeには、トランザクション輻輳制御やペイメントチャネルの具体化などの用途がある。これらについては、このBIPの「動機」のセクションで説明している。

概要

Tapscriptの実行中、CHECKOUTPUTSHASHVERIFYはopcodeOP_RESERVED1(0x89)を使用する。

CHECKOUTPUTSHASHVERIFYは以下の条件を検証する。

CHECKOUTPUTSHASHVERIFYの後の操作が、32バイトのデータプッシュではない場合、それは無視される。条件が満たされなければ、実行は失敗する。

動機

Covenantsもしくは鍵の所有権を超えたコインの使用方法に対する制限は、スマートコントラクトを構築するための非常に強力な構成要素だ。しかし、その複雑さとfungibilityのリスクをもたらす可能性を考えると、これまでBitcoinに導入するのを真剣に検討してこなかった。

このBIPの目的は、実用的な機能の限定されたセットを可能にする最小の実行可能なcovenantを導入することにある。例えば、

輻輳制御されたトランザクション

ブロックスペースに対して大きな需要があるタイミングでの支払いは非常に高価になる。CHECKOUTPUTSHASHVERIFYを使うことで、大容量のペイメントプロセッサは、承認のために、それらのすべての支払いを単一のO(1)トランザクションに集約することができる。そして、その後ブロックスペースの需要が減少したタイミングで、支払いをそのUTXOから拡張できる。

CHECKOUTPUTSHASHVERIFYがなくても、これはSchnorr署名を使って実現できる(もしくはマルチパーティスキームが与えられたECDSAでも)。しかし、非対話的に行うのは不可能で、それはアプローチの実行可能性を根本的に制限する。

ユーザーが複数の選択肢を持つように輻輳制御トランザクションを構築するために、CHECKOUTPUTSHASHVERIFYを使って1からNまでの単一のアウトプットへの支払いを保証するか、アウトプットのツリーにコミットするかして、好きなだけ多くの支払いを承認するのを可能にする。さらにTaprootha可変サイズの拡張(あるノードが2、4,8などで拡張する)にコミットできる。これによりトランザクションのオーバーヘッドとすぐに利用可能なブロックスペースの間のトレードオフが可能になる。その場合マークルツリーの検索はO(log(log(N)))の追加のオーバーヘッドとなるが、ブロックの要求に応じてそれをE[O(1)]となるようハフマン符号化することができる。ツリーの各ノードはTaproot署名ベースの使用を優先するようオプトインすることも可能だが、参加者がオフラインまたは悪意ある場合、拡大をより小さなグループで進めることができる。

このアプローチの全体的な(最適化なしの)オーバーヘッドは、各ユーザーO(log(N))トランザクションの観点から見たもので、追加のトランザクションは1つだけで、ネットワークの観点からは2N。ただし、そのようなトランザクションに必要な署名無いため、実際のオーバーヘッドは少なくなる。

以下のチャートは、これらのトランザクションと通常のトランザクションおよびバッチトランザクションの構造を比較している。

https://github.com/JeremyRubin/bips/raw/op-checkoutputshashverify/bip-coshv/states.svg?sanitize=true

5%のネットワークの採用と50%のネットワークの採用の場合で、これがmempoolバックログに与える可能性がある影響のシミュレーションを以下に示す。シミュレーション用のコードはこのBIPのサブディレクトリにある。

https://github.com/JeremyRubin/bips/raw/op-checkoutputshashverify/bip-coshv/five.png

https://github.com/JeremyRubin/bips/raw/op-checkoutputshashverify/bip-coshv/fifty.png

Channel Factories

このユースケースは、支払いの代わりにリーフノードを(おそらく、支払人と受取人の間もしくは、受取人が選択した対象との間の)チャネルとしてセットアップする必要があることを除いて、上記と同じだ。

すべての罰則は相対的なタイムロックで実際に具体化されるので、これらのチャネルは既にセットアップの時間については重要でない。

これにより、この遅延方法を使って送信されたコインの即時流動性が可能になる。

ウォレットの金庫

コールドストレージソリューションにより高い安全性が必要な場合、あるターゲットから別のターゲットへ資金を移動するデフォルトのTapscriptパスが存在する可能性がある。

例えば、コールドウォレットを、1人のカスタマーサポートデスクが追加の承認なく(複数の事前セットされた量を使って)資金の一部を、隔離されたサポートデスクによって操作されるウォレットに移動できるよう設定できる。サポートデスクはその後、いくつかの資金をホットウォレットに発行し、残りを同様の償還の仕組みを使ってコールドストレージに返送することができる。

これはCHECKOUTPUTSHASHVERIFYを使わなくても可能だが、CHECKOUTPUTSHASHVERIFYを使うことで、調整およびオンライン署名者を排除し、サポートデスクが資金を不適切い移動する能力を削減できる。

さらに、そのような全ての設計を相対的なタイムロックと組み合わせて、コンプライアンスおよびリスクデスクに介入する時間を与えることができる。

CoinJoin

この種のアプローチは、トラストレスなCoinJoinをセットアップするのを簡単にする。

すべての参加者は、そのアウトプットのハッシュにコミットする単一のUTXOに同意し、参加者はその後自分が好きなインプットでトラストレスに資金を供給することができる。

続いて、トランザクションが承認される。

必要に応じて、Tapscriptパスは、fungibilityを向上させるために署名ベースの使用によって奪われる可能性があある。

設計

CHECKOUTPUTSHASHVERIFYの目標は、既存のコードベースへの影響を最小限に抑えることだ。将来的には、より複雑になることが分かっているが、安全なユースケースであることが示されているため新しいcovenantタイプが有効になる可能性がある。

重要なのは、これはTapscriptなので、Tapscriptパスを署名で置き換えるために参加者が協同することができることを意図している。他の(チャネル状態などの)依存関係が更新される可能性がある場合、アウトプットは単独で使用されアウトプットのハッシュが正確に一致するという要件は外れる。

以下では、ルールについて1つずつ説明する。

次のスクリプトが32バイトの最小限のデータプッシュである

CHECKOUTPUTSHASHVERIFYは、opcodeの前ではなくopcodeの後のプッシュアイテムを使用する。CHECKOUTPUTSHASHVERIFYがスタックからデータを使うと、どのデータがコミットされるのかスクリプトで構成することが可能になる。データの先読みを使用することで使用時にアウトプットが確実に分かるようにする。

スクリプトプログラマーOP_IF OP_CHECKOUTPUTSHASHVERIFY <outputs 1> OP_ELSE <outputs 2> OP_ENDIF.のように、どのハッシュがチェックされるか条件付けすることができる。ただし、アウトプットをリテラルのハッシュにしておくことで可能性が制限される。

いずれにせよ、TapscriptのAPIを考えると、ユーザーは複数のOP_CHECKOUTPUTSHASHVERIFY操作を含むコードを別々のブランチにコンパイルする可能性が高い。

トランザクションで使用されるインプットは1つだけ

トランザクションで複数のインプットを使用できるようにすると、2つのアウトプットが同じアウトプットのセットへの支払いを要求する可能性があり、その結果意図した支払いの半分が破壊される。複数のインプットを許可する安全な方法はあるが、設計ははるかに複雑で、ユースケースはあまり明確でない。

さらに安定したTXIDが必要とされるペイメントチャネルの構成にとっては、どのインプットを同時に使用できるかという制限は非常に重要だ。

シリアライズされたアウトプットのSHA256doubleハッシュが指定された値と一致する

これは既に計算されているハッシュであるため、余分な検証のオーバーヘッドを回避できる。したがって、OP_CHECKOUTPUTSHASHVERIFYでは追加の検証オーバーヘッドが大幅に増加することはない。

このハッシュをスタックに公開することで、アウトプットの解析が可能になる可能性を心配する必要はない。それらはスクリプトの構築時に既に正確に分かっているため。

設計のトレードオフとリスク

Covenantsは、それがもたらすfungibilityのリスクの可能性から、歴史的に物議を醸してきた - コインがどのように使われるか、使われないかという。

ここに提示されたアプローチでは、Covenantsは以下のように厳しく制限される。全てのCovenantsは、Covenantsの要件よりも優先されるマルチシグベースの鍵でラップされる。さらに、OP_CHECKOUTPUTSHASHVERIFY Covenantsの構造は、その送信先となるアウトプットが正確に分かっている必要がある。したがって、作れるのは有限数のステップで拡大するCovenantsのみで、これは、安全という意味では、到達可能な最終状態ですべてのインプットを作成するトランザクションのセットに相当する。さらに、Covenantsは単一のインプットとしてのみ使用可能なように制限されており、「半分だけ使用」問題を防いでいる。

これらのCovenantsは、その制限のように、いくつかのリスクを負っている。OP_CHECKOUTPUTSHASHVERIFYに提供されるハッシュのプリイメージが分からなかったり、Taprootが未知の秘密鍵の公開鍵で構成されているCovenantsの可能性もある。アドレスが使用可能かどうか知ることと、送信者の任意のアドレスへ支払いする能力とは互換性がない(特にOP_RETURN)。送信者が送金する前に受信者がcovenantsを削除できることを知る必要があるなら、送信者は受取人からチャレンジ文字列の署名を要求するかもしれない。最後のリスクは「転送アドレスコントラクト」の悪用だ。転送アドレスは事前定義された方法で自動的に実行できるスクリプトだ。例えば、ホットウォレットには、相対的なタイムアウト後に自動的にコールドストレージアドレスに移動できるコインがある場合がある。問題は、そのような鍵を再利用するのはとても安全ではないということだ。例えば、1 BTCをコールドストレージに転送するアドレスを作成するとする。1 BTC未満でこのアドレスのアウトプットを作成すると、Taprootの署名パスが使われるまでフリーズすることになる。1 BTC以上がそのアドレスに支払われ、redeemscriptが公に知られている場合、誰でも1 BTCの超過分の資金を大きなマイナー手数料として支払うことができる。再利用可能な鍵をより安全に使用できるようにするために、最大の手数料額にコミットするopcodeや他の制限を後で導入することは可能だ。今のところ、すべてのブランチが希望する支払いと互換性があることが確実でない限り、Taprootキーを再利用しないことが最善だ。この制限とリスクはOP_CHECKOUTPUTSHASHVERIFY固有のものではない。Taprooスクリプトには、複数回使用するのが安全でない論理分岐が多数含まれる可能性がある(ハッシュタイムロック分岐は使用するたびにユニークなハッシュで具体化する必要がある)。

MES16(Eminの提案)で提案されたようなより強力なCovenantsが実装されると、OP_CHECKOUTPUTSHASHVERIFYタイプのCovenantsは不要になるだろう。それらは、 child-pays-for-parentや他の仕組みに頼らず手数料を調整する能力を向上させるという点でいいくつかの利点をもたらすだろう。しかし、これらの機能はかなり複雑さを増し、意図しない動作をする余地がある。或いは、CHECKSIGFROMSTACKSIGHASH_NOINPUTベースのCovenants設計でもCovenantsを実装することは可能だ。SIGHASH_NOINPUTには、Bitcoinへの導入を阻む、追加のリスクがある。CHECKSIGFROMSTACKは使うのがより複雑で、OP_CHECKOUTPUTSHASHVERIFYにはない追加の検証オーバーヘッドがある。実装および分析するためのこのアプローチの単純さおよびユーザーアプリケーションによって実現可能な利点を考慮すると、OP_CHECKOUTPUTSHASHVERIFYアプローチが提案される。

仕様

以下のコードはOP_CHECKOUTPUTSHASHVERIFYを検証するための主なロジック。

case OP_CHECKOUTPUTSHASHVERIFY:
{
    // 有効になる前は検証しない
    if (flags & SCRIPT_VERIFY_OUTPUTS_HASH) {
        CScript::const_iterator lookahead = pc;
        opcodetype argument;
        // 先読みの引数として1opcodeを先読み
        if (!script.GetOp(lookahead, argument, vchPushValue))
            return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
        // 先読みの引数が正確に32バイトの場合、OutputHashをチェック
        // これは後でこのopcodeに異なる意味を追加できるようにするため。
        if (vchPushValue.size() == 32) {
            // 引数は0x20であるべきで(MinimalPush)、その他は失敗する
            if (!CheckMinimalPush(vchPushValue, argument)) {
                return set_error(serror, SCRIPT_ERR_MINIMALDATA);
            }
            // 複数のインプットが許可されている場合、同じOutputsHashVerifyを持つ2つのインプットが意図した金額半分だけしはらうことになる。
            if (!checker.CheckOnlyOneInput()) {
                return set_error(serror, SCRIPT_ERR_OUTPUTSHASHVERIFY);
            }
            // 最後にアウトプットのハッシュが渡された値と一致することを確認する。
            if (!checker.CheckOutputsHash(vchPushValue)) {
                return set_error(serror, SCRIPT_ERR_OUTPUTSHASHVERIFY);
            }
        }
    }
}
break;

展開

展開は、Tapscriptで行われることを意図している。

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

実装

実装とテストは以下で入手可能。

https://github.com/JeremyRubin/bitcoin/tree/congestion-control

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ドラフトbip-tapscript

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

techmedia-think.hatenablog.com

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

https://github.com/sipa/bips/blob/bip-schnorr/bip-tapscript.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ドラフトの内容。

イントロダクション

概要

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

動機

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

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

設計

これらの目標を達成するために、署名opcode、OP_CHECKSIGおよびOP_CHECKSIGVERIFYは、taprootのトランザクションダイジェストをベースにした新しいトランザクションダイジェストを使用し、bip-schnorrで定義されたSchnorr署名を検証するよう変更される。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 16のredeem scriptがBIP 141で定義されているようなwitness programである)。
  • そのアウトプットはbip-taprootで定義されているtaprootアウトプットである(つまり、witness version 1で、witness programは33バイト、そしてその先頭バイトは0x00もしくは0x01である)。
  • bip-taprootで定義されたスクリプトパスを使用している(つまり、witnessスタックからオプションのannexを削除後、2つ以上のスタック要素が残ること)。
  • リーフバージョンは0x00(オプションのannexを削除後、最後のwitness要素の先頭バイトが0xc0もしくは0xc1である)、つまりTapscriptの使用としてマークされている。

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

  1. インプットがBIP 16, BIP 141もしくはbip-taprootに対して無効な場合、失敗する。
  2. bip-taprootで定義されているスクリプト(つまりオプションの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-taprootで定義されている初期スタック内の任意の要素のサイズが(つまり、オプションのannexおよび最後から2つのスタック要素を両方削除した後のwitnessスタックのサイズ)520バイトを超える場合、失敗する。
  4. tapscriptが10000バイトを超える場合、失敗する。
  5. tapscriptは、初期スタックをインプットとして次のセクションのルールに従って実行される。
    1. 何らかの理由で実行に失敗した場合(201回の非push opcodeの制限を含む)、失敗する。
    2. 実行した結果、スタック上の要素が正確に1つで、それがCastToBool()でtrueとして評価される要素以外が発生した場合、失敗する。
  6. 失敗すること無くこのステップに到達すると、検証は成功となる。

スクリプトの実行

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

  • 無効化されたスクリプトopcode 次のscript opcodeはtapscriptでは無効になっている:OP_CHECKMULTISIGおよびOP_CHECKMULTISIGVERIFY。無効化されたopcodeは、実行されるとすぐに終了し、未実行ブランチで見つかっても無視するという点でOP_RETURNと同じように動作する。無視されていても、201回の非push opcodeの制限にカウントされる。
  • コンセンサスによる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の場合、スクリプトは失敗し直ちに終了しなければならない。
  • 公開鍵の先頭バイトが0x040x06および0x07の場合、公開鍵のサイズに関係なくスクリプトは失敗し直ちに終了しなければならない。
  • 公開鍵の先頭バイトが0x02もしくは0x03の場合、bip-schnorrで定義されているように公開鍵とみなされる。
    • 公開鍵が33バイトでない場合、スクリプトは失敗し直ちに終了しなければならない。
    • 署名が空のベクターでない場合、公開鍵およびメッセージであるtapscriptトランザクションダイジェスト(以下に定義)に対して、bip-taprootの署名検証ルールに従って検証される。検証に失敗するとスクリプトは失敗し直ちに終了しなければならない。
  • 公開鍵の先頭バイトが0x020x030x040x060x07のいずれでもない場合、公開鍵は未知の公開鍵タイプであり、実際の署名検証は適用されない。署名opcodeのスクリプト実行中、署名検証が成功したとみなされることを除き、既知の公開鍵タイプと同じように動作する。
  • 公開鍵のタイプに関係なく、この手順の前にスクリプトが失敗し終了しなかった場合は:
    • 署名が空のベクターの場合
      • OP_CHECKSIGVERIFYの場合、スクリプトは失敗し直ちに終了しなければならない。
      • OP_CHECKSIGの場合、空のベクターがスタックにプッシュされ、次のopcodeへ実行が続く。
      • OP_CHECKSIGADDの場合、値nCScriptNumがスタックにプッシュされ、次のopcopdeへ実行が続く。
    • 署名が空のベクターでなく、sigops_passedカウンターがインクリメントされる場合
      • OP_CHECKSIGVERIFYの場合、スタックのこれ以上の変更なく実行は継続する。
      • OP_CHECKSIGの場合、1バイトの値0x01がスタック上にプッシュされる。
      • OP_CHECKSIGADDの場合、値n + 1CScriptNumがスタックにプッシュされる。

これらのopcodeは201回の非push opcode制限にカウントされる。

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

署名opcodeの署名検証のためのメッセージとして、トランザクションダイジェストは以下を除いてbip-taprootと同じ定義である。

1バイトのspend_typeはビット2で指定される異なる値を持つ:

  • 使用されるscriptPubKeyが(native segwitではなく)P2SHである場合、ビット0がセットされる。
  • (元のwitnessスタックにはすくなくとも2つのwitness要素があり、最後の要素の先頭バイトが0x50である)annexが存在する場合、ビット1がセットされる。
  • ビット2ガセットされる。
  • 他のビットはセットされない。

 {hash_{TapSighash}}関数への入力の最後に追加される追加のデータ:

  • tapleaf_hash(32): bip-taprootで定義されるtapleafハッシュ
  • key_version(1): tapscript署名opcode実行において、現在の公開鍵のバージョンを表す定数値0x02
  • codeseparator_position(2): 現在実行されている署名opcodeの前に最後に実行されたOP_CODESEPARATORのopcodeの位置のリトルエンディアン表記(実行されていない場合は、0xffff)。スクリプト内の最初のopocdeの位置は0。マルチバイトpush opcodeはプッシュされるデータのサイズに関係なく1つのopcodeとしてカウントされる。

ハッシュされる総バイト数は最大244。

要約すると、BIP 143sighash typeのセマンティクスは以下を除いて変更されていない。

  1. 例外はbip-taprootに記載されている。
  2. ダイジェストはtaproot固有のデータkey_versionをコミットする。
  3. ダイジェストは、scriptCodeの代わりにleaf versionとスクリプトを含むtapleaf_hashを介して実行されるスクリプトにコミットする。
  4. ダイジェストは、最後に実行されたOP_CODESEPARATORの位置にコミットする。

署名opcodeの制限

201回の非push opcodeの制限に加えて、署名opcodeの使用はさらに制限を受ける。

  • input_witness_weightが、特定のトランザクションインプットに関連付けられているシリアライズされたインプットのwitnessのサイズとして定義される。BIP 141で定義されているようにシリアライズされたインプットのwitnessは要素数および各要素のサイズ、各要素の内容を示すCCompactSizeタグが含まれている。input_witness_weightは、CCompactSizeタグと要素の内容の合計サイズである。
  • sigops_passedが、正常に実行された署名opcodeの合計数として定義される。これらの署名サイズはゼロ以外で、失敗してスクリプトを終了させるものではない。誤解を避けるため、未知のタイプの公開鍵とゼロ以外のサイズの署名を署名opcodeに渡してもsigops_passedにカウントされる。
  • 50 * (sigops_passed - 1)input_witness_weightより大きい場合、スクリプトは失敗し直ちに終了しなければならない。

このルールは、従来のP2WSHスクリプトにのみ適用されるsigopsの制限と同様にtapscriptのワーストケースの検証コストを制限する。

論拠

  1. どうして0xcが選択されたのか? bip-taprootのガイドラインにしたがって、2つの最上位ビットをセットした値を選択することで、使用するUTXOにアクセスすることなく使用する量を識別することができる。
  2. OP_SUCCESSx OP_SUCCESSxスクリプトシステムをアップグレードするための仕組みだ。ソフトフォークによって意味が定義される前にOP_SUCCESSxを使うことは、安全ではなく、資金の損失に繋がる。スクリプト内にOP_SUCCESSxを含めると、無条件にコインを渡すことができる。これはさまざまなエッジケースを定義する際の問題を回避するためにスクリプトの実行ルールより優先される。例えば、OP_SUCCESSxが202回目のopcodeになったり、OP_SUCCESSxの後にたくさんの署名opcodeがあったり、OP_ENDIFを書いた条件付きスクリプトであっても。スクリプトのどこかにOP_SUCCESSxがあるだけで、そのようなすべてのケースでパスすることが保証される。OP_SUCCESSxは非常に初期バージョン(v0.1〜v0.3.5まで)のOP_RETURNに似ている。オリジナルのOP_RETURNスクリプトの実行をすぐに終了し、終了時のトップスタックの要素に基づいて成功か失敗かを返していた。これだとscriptSigにOP_RETURNを入れることで無条件に第三者による窃盗が可能であったため、オリジナルのBitcoinプロトコルの大きな設計上の欠陥の1つだった。OP_SUCCESSxスクリプトの一部であり(したがってtaprootアウトプットにコミットしている)、コイン所有者の同意を暗示しているため、第三者が検証プロセスにOP_SUCCESSxを挿入することは不可能であるため、これは本提案では問題にならない。OP_SUCCESSxはさまざまなアップグレードの可能性に使用できる。
    • OP_SUCCESSxはソフトフォークで機能的なopcodeに変換することができる。スタックへの読み取り専用アクセスしかできないOP_NOPx派生opcodeとは違って、OP_SUCCESSxはスタックへの書き込みも可能だ。OP_SUCCESSxを含むスクリプトの変更は、有効なスクリプトが無効なスクリプトになるだけで、これはソフトフォークで常に実現できる。
    • OP_SUCCESSxは初期スタックとpush opcodeのサイズチェックに先行するので、OP_SUCCESSx派生opcodeはソフトフォークで520バイトを超えるスタック要素を可能にするかもしれない。
    • また、OP_SUCCESSxは既存のopcodeを再定義し、新しいopcodeとの連携を可能にする。例えば、OP_SUCCESSx派生opcodeが64ビット整数で機能する場合、同じスクリプト内の既存の算術opcodeもでも同じことが可能になる。
    • OP_SUCCESSxはパースできない可能性があるスクリプトもパスさせるので、マルチバイトopocdeの導入や、OP_SUCCESSxプレフィックスにした完全に新しいスクリプト言語を導入するのに使うことができる。
  3. MINIMALIFをコンセンサスにするのはなぜ? こうすることで、スタックからブランチ情報を取得するmalleabilityの無いコードを書くのがかなり簡単になる。
  4. OP_CHECKSIGADD このopcodeはバッチ検証とは互換性のないOP_CHECKMULTISIGのようなopcodeが無くなるのをカバーするために追加されている。OP_CHECKSIGADDは機能的にはOP_ROT OP_SWAP OP_CHECKSIG OP_ADDと同等だが、201回の非push opcode制限に対して1つのopcodeとしてカウントされる。OP_ADDCScriptNum関連の振る舞いはすべてOP_CHECKSIGADDにも適用できる。
  5. CHECKMULTISIGとCHECKSIGの比較 witnessとして0 <signature_1> ... <signature_m>を持つ、CHECKMULTISIGスクリプトm <pubkey_1> ... <pubkey_n> n CHECKMULTISIGは、witness <w_1> ... <w_n>を持つスクリプト<pubkey_1> CHECKSIG ... <pubkey_n> CHECKSIGADD m NUMEQUALに書き換えることができる。各witness要素w_iは同じインデックスの公開鍵に対応する署名か空のベクターのいずれかである。同様にCHECKMULTISIGVERIFYスクリプトは、NUMEQUALNUMEQUALVERIFYに置き換えることでbip-tapscriptに変換できる。別の方法として、スクリプトをマークルツリーのいくつかのリーフに分割し、それぞれが<pubkey_1> CHECKSIGVERIFY ... <pubkey_(n-1)> CHECKSIGVERIFY <pubkey_n> CHECKSIGを使ってm-of-mポリシーを実装することで、m-of-nマルチシグポリシーを実装できる。この設定で参加者が署名しながら対話的にコラボレーションできる場合、マルチシグポリシーはm-of-mではMuSigを使い、m-of-nでは検証可能な秘密共有使って閾値署名を実現できる。
  6. 未知のタイプの公開鍵はソフトフォークを介して新しい署名検証ルールの追加を可能にする。ソフトフォークは、スクリプトをパスするか失敗してすぐに終了させる実際の署名検証を追加できる。この方法で、NOINPUTタグ付き公開鍵と署名検証用のtaprootの内部キーによって置き換えられる公開鍵定数と同様に新しいSIGHASHモードを追加できる。
  7. signature hash用にハッシュされたバイト数は?  {hash_{TapSighash}}への入力の合計サイズは(最初の64バイトのハッシュタグを除く)、次の式を使って計算できる。212 - is_anyonecanpay * 50 - is_none * 32 - is_p2sh_spending * 12 + has_annex * 32
  8. トランザクションダイジェストがkey_versionにコミットするのはなぜ? これは未知の公開鍵タイプを定義する将来の拡張のためのもので、署名をある鍵タイプから別のタイプに移動できないようにするため。この値は、Y座標の偶奇のようなフラグをマスキングした後の公開鍵の先頭バイトと等しくなるよう設定されることを意図している。
  9. トランザクションダイジェストが最後に実行されたOP_CODESEPARATORの位置にコミットするのはなぜ? これによりOP_CODESEPARATORを引き続き使用してスクリプトの実行パスに署名できる。codeseparator_positionがダイジェストへの最後の入力なので、SHA256のmidstateが単一のスクリプトで複数のOP_CODESEPARATORに対して効率的にキャッシュできる。対照的に、BIP 143のOP_CODESEPARATORのハンドリングは最後に実行されたOP_CODESEPARATOR以降から実行されたスクリプトにコミットするため、スクリプトの不必要な再ハッシュが必要になる。2つのコードブランチ間で最初の公開鍵を共有することで、2つ目の公開鍵プッシュをスクリプトに保存するというOP_CODESEPARATORの既知のユースケースは、各ブランチを別々のtaprootリーフに移動することでさらに安価に表現できる。
  10. tapscriptのsigop制限 署名opcodeの制限は、過度に多い署名操作によって検証が遅くなることからスクリプトを保護する。tapscriptでは、署名opcodeの数はBIP141または従来のsigop制限にはカウントされない。従来のsigop制限は、トランザクションweightに加えて2つめの制約であるため、ブロックを構成する際のトランザクションの選択を不必要に難しくする。代わりに、tapscriptの署名opcodeの数はwitnessのweightによって制限される。さらに制限はブロックではなくトランザクションのインプットに適用され、実際に実行された署名opcodeのみがカウントされる。tapscriptの実行では、50 witness weightあたり1つの署名opcodeを許可し、プラス1つの無料の署名opcodeが許可される。tapscriptの署名opcodeの制限により、CHECKSIGFROMSTACKのような新しい署名opcodeを追加し、ソフトフォークを介して制限にカウントすることができる。将来、通常のスクリプトコストを変更する新しいopcodeが導入されたとしても、witnessに無意味なデータを詰め込む必要がある。その場合、実際のwitnessサイズを増やすこと無くwitnessのweightを増やすためにtaproot annexを使用することができる。
  11. sigop制限のパラメータ選択 通常のwitnessはそのweightが公開鍵と(SIGHASH_ALL)署名のペアの34 + 65 weight単位で構成されているため(1 weightのCCompactSizeタグを含む)、制限の影響を受けない。署名のweightだけで65もしくは66 weightの単位になるため、公開鍵がスクリプトで再利用されている場合も同様。ただし、この制限により追加のweightが必要となり、重複した署名(および公開鍵)を持つ異常なスクリプトの手数料が増加する。sigopファクター50あたりのweightはBIP 141のブロックリミットの比率(4MBのweightを80,000 sigopで割ったもの)に相当する。制限によって許可された無料の署名opcodeは、トランザクションインプットの非witness部分のweightを考慮するために存在する。

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

先日、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-taprootの訳(ドラフトなので、MLなどでの議論を経てこれから更新されていく)。わりと途中でこの仕様、要素何のためにあるの?と疑問に思うところがあるけど、論拠と合わせて読むといい。

動機

Bitcoinスクリプト機能を改善するためのアイディアがこれまでいくつか提案されている。

  • Schnorr署名(bip-schnorr)
  • マークルブランチ(MAST, BIP-114,117)
  • 新しいSIGHASHモード(BIP-118)
  • CHECKSIGFROMSTACKのような新しいopcode
  • Taproot
  • Graftroot
  • G'root
  • インプットをまたぐ集約

これらの全てのアイディアを1つの提案にまとめるのは、大規模な変更となり、レビューが大変になり、そうでなければ途中で実現されていたかもしれない新しい発見を見逃す可能性がある。これらのアイディアの中には、他のものより成熟度が低いものもある。一方、すべての個々の独立した提案に分割すると、効率とプライバシーの利点が現象し、相互作用の分析が複雑になる。一度に一つのゴールセットにフォーカスし、それらを達成するために相互作用する技術を組み合わせることが望ましい。

設計

この提案は、以下の2つの制限を条件として、Bitcoinのスマートコントラクトのプライバシー、効率性、柔軟性の改善にフォーカスする。

  • 新しく強力なセキュリティ仮定を追加しない。
  • 独立して単純に実装される可能性がある機能を提案に組み入れない。

具体的には、トランザクションアウトプットの使用条件に関する情報が、作成時または使用時にチェーン上で明らかになる際に、その情報の量を最小限にすることを目的としている。将来の改善の有効性を損なわないようにするため、マイナーだが長年の問題に対する修正と同様に、いくつかのアップグレードの仕組みも含まれる。

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

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

この提案に含まれていないのは、将来の拡張としての有効性がある新しいSIGHASHモードやopcodeのような追加機能だ。またインプットをまたいだ集約も含まれていない。これはアップグレードの仕組みと複雑な方法で相互作用するため、それに対する解決策が依然として流動的であるため。

仕様

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

以下の表記はbip-schnorrの表記に従う。

タグ付きハッシュ

暗号学的ハッシュ関数は、以下の仕様とBitcoinで一般的な複数の目的で使用される。あるコンテキストで使用されたハッシュが別のコンテキストで再解釈されないようにするため、全てのハッシュ関数はコンテキスト依存のタグ名で調整され、コンテキスト間の衝突は実行不可能であると見なす。

以下のテキストでは、 {hash_{tag}(m)} {SHA256(SHA256(tag) || SHA256(tag) || m)}の短縮表記で、tagはUTF-8エンコードされたタグ名を指す。

  • これまでのところ、BitcoinプロトコルのどこにもSHA256の入力が2つの(ダブルではない)SHA256ハッシュで始まるハッシュはどこにもなく、ハッシュ関数の既存の使用との衝突を不可能にしている。
  • プレフィックスのSHA256(tag) || SHA256(tag) は64バイト長のコンテキスト固有の定数で、最適化された実装が可能(SHA256自体と同じだが、初期状態が変更されている)。
  • tag名自体にSHA256を使用するのは、上記の最適化の使用を選択しない実装にとって合理的で単純で効率的だ。

Scriptの検証ルール

Taprootのアウトプットはバージョン番号1のsegwitアウトプットで、33バイトのwitness programを持ちその先頭バイトは0もしくは1。以下のルールは、そのようなアウトプットが使用される場合にのみ適用される。33バイト以外の長さや先頭バイトが0, 1ではないバージョン1のアウトプットの場合、ルールは適用されず誰もが使用できる。

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

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

署名検証ルール

以下のルールが適用される。

  • 署名が64バイトもしくは65バイトでない場合、失敗する。
  • 署名のサイズが65バイトの場合、
    • 最終バイトが有効なhash_type(後述)でない場合、失敗する。
    • 最終バイトが0x00の場合、失敗する。
    • 先頭64バイトが、最終バイトとしてhash_typeをセットしたトランザクションダイジェストをメッセージとし、それと公開鍵に対してbip-schnorrによる有効な署名ではない場合、失敗する。
  • 署名のサイズが64バイトの場合、
    • hash_type = 0x00トランザクションダイジェストをメッセージとし、それと公開鍵に対してbip-schnorrによる有効な署名ではない場合、失敗する。
  • それ以外の場合、署名は有効である。
hash_type

hash_typeは8bitの符号なしの値。SIGHASH_ALL, SIGHASH_NONE, SIGHASH_SINGLE, SIGHASH_ANYONECANPAYを含む従来のスクリプトシステムのSIGHASHエンコーディングが使用される。

以下のhash_typeの使用は無効であり、実行が失敗する。

  • 対応するアウトプット(検証されるインプットと同じインデックスを持つアウトプット)が無いのに、SIGHASH_SINGLEを使用する。
  • 0x00, 0x01, 0x02, 0x03, 0x81, 0x82, 0x83以外のhash_typeを使用する。
  • 署名が65バイトで、hash_type0x00の場合。
Transaction digest

署名検証のメッセージとして、トランザクションダイジェストは以下の値を(バイト単位のサイズで)シリアライズされた {hash_{TapSighash}}になる。2,4または8バイトの数値はリトルエンディアンでエンコードされる。

  • Control:
    • epoch (1): 常に0
    • hash_type (1)
  • トランザクションデータ
  • このインプットのデータについて
    • spend_type(1):
      • 使用するscriptPubKeyがP2SHである場合はビット0をセットする。
      • annexが存在する場合、ビット1をセットする(元のwitnessスタックに2つ以上のwitness要素があり、最後の要素の最初のバイトが0x50)。
      • 他のビットはセットされない。
    • scriptPubKey(24 or 36): このインプットが使用する前のアウトプットのscriptPubKeyで、CTxOut無いのスクリプトとしてシリアライズされる。サイズはP2SHに埋め込んだSegwitの場合24バイトで、ネイティブSegwitの場合は36バイト。
    • SIGHASH_ANYONECANPAYフラグがセットされている場合、
      • outpoint(32): このインプットのCOutPoint(32バイトのハッシュ + 4バイトのリトルエンディアン)
      • amount(8): このインプットで使われる前のアウトプットのコインの量
      • nSequence(4): このインプットのnSequence
    • SIGHASH_ANYONECANPAYフラグがセットされていない場合
      • input_index(2): トランザクション無いでこのインプットのインデックス。最初のインプットのインデックスは0。
    • spend_typeにビット1がセットされている場合、
      • sha_annex(32): (compact_size(annexのサイズ) || annex)のSHA256ハッシュ値
  • このアウトプットのデータについて
    • SIGHASH_SINGLEフラグがセットされている場合
      • sha_single_output(32): 対応するアウトプットのCTxOutフォーマットのSHA256ハッシュ値

ハッシュされたバイト数の合計は最大209。

要約すると、BIP 143のセマンティクスはSIGHASHタイプは変更しないまま、以下を除いて変更されない。

  • リアライゼーションの方法と順番が変更される。
  • ダイジェストはscriptPubKeyにコミットする。
  • SIGHASH_ANYONECANPAYフラグがセットされない場合、ダイジェストはすべてのインプットの量にコミットする。
  • SIGHASH_NONEもしくはSIGHASH_SINGLEがセットされていた場合(SIGHASH_ANYONECANPAYがセットされていない限り)、ダイジェストはすべてのインプットのnSequenceにコミットする。
  • ダイジェストはTaproot固有のデータepochspend_type、(あれば)annexにコミットする。

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

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

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

初期ステップ

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

  • (OP_IFなどの)条件付きのスクリプトを決定し、それらを複数のスクリプトに分割する(1つ1つが元のスクリプトの実行パスに対応する)際は、通常後半の選択を推奨する。
  • 単一の条件が複数の鍵の署名を要求する場合は、MuSigのような鍵集約技術を使ってそれらを単一の鍵に集約することができる。詳細はこのドキュメントの範囲外だが、この方法は署名手順を複雑にする可能性があることに注意すること。
  • 1つ以上の使用条件が(集約後の)単一の鍵のみで構成されている場合、最も使用する可能性が高いものを内部キーにする必要がある。そのような条件がない場合は、全てのスクリプトに参加している全ての鍵を集約したもので構成される鍵を追加するのを推奨する。これにより全員が同意するというブランチを効果的に追加する。それが許容できない場合は未知の離散対数を持つ点を内部キーとする(TODO)。
  • 残りのスクリプトは、二分木のリーフに編成する必要がある。スクリプトの各条件が同じようにリーフに対応する場合、ツリーはバランスがとれたツリーになる。各条件の確率が分かっている場合は、ツリーをハフマン木として構築することを検討してほしい。

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

使用条件が内部キーinternal_pubkeyとリーフが(leaf_version、スクリプト)のタプルである二分木に分割されると、以下のPython3 アルゴリズムを使ってアウトプットスクリプトを計算できる。以下のコードでは、ser_scriptプレフィックスはその入力のCCompactSizeエンコードされた長さを前に付与し、public keyオブジェクトはその圧縮エンコード(bip-schnorr参照)を取得するget_bytesメソッドと、公開鍵にsecp256k1ジェネレータの倍数を追加するためのtweak_addメソッドを持つ。

import hashlib

def tagged_hash(tag, msg):
    tag_hash = hashlib.sha256(tag.encode()).digest()
    return hashlib.sha256(tag_hash + tag_hash + msg).digest()

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-tapscript scripts)
     - a list of two elements, each with the same structure as script_tree itself"""
    _, h = taproot_tree_helper(script_tree)
    t = tagged_hash("TapTweak", internal_pubkey.get_bytes() + h)
    assert int.from_bytes(t, 'big') < SECP256K1_ORDER
    output_pubkey = internal_pubkey.tweak_add(t).get_bytes()
    return bytes([0x51, 0x21, output_pubkey[0] & 1]) + output_pubkey[1:]

関数taproot_output_scriptは、scriptPubKeyのバイト列を返す。必要に応じてP2SHでラップ可能(BIP-141参照)。

https://github.com/sipa/bips/raw/bip-schnorr/bip-taproot/tree.png

この図は、内部キーPと3つのスクリプトリーフからなるマークルツリーからtweakを入手するためのハッシュ構造を示している。

内部キーを使ったコインの使用

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

def taproot_sign_internal_key(internal_pubkey, script_tree, internal_privkey, hash_type):
    _, h = taproot_tree_helper(script_tree)
    t = tagged_hash("TapTweak", internal_pubkey.get_bytes() + h)
    output_privkey = internal_privkey.tweak_add(t)
    sig = output_privkey.sign_schnorr(sighash(hash_type))
    if hash_type != 0:
        sig += bytes([hash_type])
    return [sig]

この関数は、tweak_addメソッドが秘密鍵を導出し、sighash関数が上記で定義されたsignature hashを計算し、必要なwitnessスタックを返す(簡単にするため、上記のスニペットトランザクションやインプットの位置、P2SHかどうかといった情報をsighashのコードへの受け渡しを無視している)。

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

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

def taproot_sign_script(internal_pubkey, script_tree, script_num, inputs):
    info, _ = taproot_tree_helper(script_tree)
    (leaf_version, script), path = info[script_num]
    pubkey_bytes = internal_pubkey.get_bytes()
    pubkey_data = bytes([(pubkey_bytes[0] & 1) + leaf_version]) + pubkey_bytes[1:]
    return inputs + [script, pubkey_data + path]

安全性

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

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

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

論拠

  1. 公開鍵がアウトプットに直接含まれるのはなぜか? 初期の典型的な構造ではスクリプトや公開鍵のハッシュをアウトプットに格納するが、公開鍵が常に含まれている場合これはかなり無駄になる。バッチ検証が可能であることを保証するために、Qは全ての検証者に知られている必要があり、したがって出力としてそのハッシュを明らかにすることだけが、witnessに追加の32バイトを追加することを意味する。さらに、アウトプットに対して128ビットの衝突の安全性を維持するためには、とくかく256ビットのハッシュが必要になる。これはサイズが(送信者にとってはコストが)、直接公開鍵を明らかにするのに匹敵する。公開鍵ハッシュの使用は、ECDLPの破壊や量子コンピューターへの保護になるとよく言われるが、その保護は非常に弱い。トランザクションは承認される間保護されず、通貨の供給の大部分はそのような保護の対象にならない。そのようなシステムへの実際の抵抗は、異なる暗号仮定頼ることで実現できる。しかしこの提案は、セキュリティモデルを変更しない改善にフォーカスしている。P2SHでラップされたアウトプットを使うと80ビットの衝突安全性しか得られないことに注意すること。これは低いとみなされ、アウトプットに複数の関係者からのデータが含まれている場合は常に関連がある。
  2. annexの先頭バイトが0x50なのはなぜか? 0xc0-0xc1定数と同様に、有効なP2WPKHもしくはP2WSHのアウトプットと混同しないように0x50を使っている。Control Blockの先頭バイトの最下位ビットが公開鍵のYの偶奇を示すのに使われるため、各スクリプトのバージョンはP2WPKHおよびP2WSHの使用時にまだ使われていない2つのサブシーケンスバイトを必要とする。annexを示すには、0x50のようにペアでない有効なバイトのみが必要だ。この選択は将来のスクリプトバージョンのために利用可能なオプションを最大にする。
  3. annexの目的は何か? annexは、使用されるアウトプットについて知らなくても認識できるような方法で、計算的に高価な新しいopcodeの検証コストを示すなど、将来の拡張のために予約されたスペースだ。このフィールドの意味が他のソフトフォークによって定義されるまでは、ユーザーはトランザクションannexを含めるべきではなく、或いは永久的な資金の喪失に繋がるかもしれない。
  4. Control Blockの先頭バイトの用途は? Control Blockの先頭バイトには3つの異なる機能がある:
    • 下位ビットは点PのY座標の偶奇を示すために使われる。
    • 上位2ビットにtrueをセットしておくことで、使用されるUTXOの知識が無くてもスクリプトを認識できるようになるため、分析が簡単になる。これは、P2WPKHやP2WSHを使用する際には最後のスタック要素の先頭バイトがそのような値になることは無いため。
    • 残りの5ビットは実際に実行されない限り観測できない新しいスクリプトバージョンを導入するのに使われる。
  5. マークルツリー内でハッシュされる前に子要素がソートされるのはなぜ? そうすることでマークルブランチのハッシュを明らかにする際に一緒に左右の方向を明らかにする必要がなくなるため。これが可能なのは、ツリー内の特定のスクリプトの位置を実際には気にしておらず、それらが実際にコミットされているということだけを気にしているためだ。
  6. 内部のマークルノードにとってもっと効率的なハッシュ構造を使わないのはなぜ? 選択された構造は、SHA256圧縮関数を2回呼び出す必要がなく、2回のうち1回は理論的に理論的に避けることができる(BIP-98参照)。しかし、実装の単純さと分析可能性のため、標準の暗号プリミティブを使って実装できる構造に固執するのが好ましいように思われる。必要なら、64バイトの入力用に特化することで2つ目の圧縮関数の大部分を最適化できる。
  7. 使用するスクリプトパスに適用できるスクリプトルールは何? bip-taprootはリーフバージョンが0xc0の場合に適用される有効性ルールを指定するが、将来の提案では他のリーフバージョンに対してルールを導入できる。
  8. 2つの署名の長さを許可するのはなぜ? 最も一般的なタイプのhash_typeを暗黙的にすることで、多くの場合1バイト節約できる。
  9. 65バイトの署名でhash_typeを0x00できないのはなぜ? これを許可すると64バイトの署名を65バイトに変更することができ、作者が意図したものとは異なる手数料率になる。
  10. なぜ未知のhash_typeを拒否するのか? そうすることで、十分なキャッシングを持つ実装が実行しなければならない署名ハッシュのワーストケースを推論するのが簡単になる。
  11. epochの用途は? epochを増加させることで、必要に応じてhash_typeの解釈を変更したり、構造を大幅に変更する新しいトランザクションダイジェストアルゴリズムを安全に作成できるようにする。
  12. signature hashのためにハッシュされたバイト数は?  {hash_{TapSighash}}への入力の(最初の64バイトのハッシュタグを除く)合計サイズは次の式で計算できる。177 - is_anyonecanpay * 50 - is_none * 32 - is_p2sh_spending * 12 + has_annex * 32
  13. トランザクションダイジェストのシリアライゼーションが変更されたのはなぜ? ダイジェストに入るハッシュとダイジェスト自身がdouble SHA256ではなく単一のSHA256で計算されるようになった。SHA256を二回しても安全性の向上は期待できない。double SHA256は伸長攻撃への保護になるのみで、トランザクションダイジェストには秘密のデータは無いので意味がない。したがって、SHA256を2回するのはリソースの無駄である。ダイジェストの計算は、最初にトランザクションデータ、次にインプットのデータ、アウトプットのデータと論理的な順に行われる。これによりSHA256のmitstateを使って、異なるインプットに渡るトランザクションのダイジェストの一部を効率的にキャッシュできる。さらにダイジェスト計算では、ハッシュの前に一部にゼロをセットするBIP 143ダイジェストとは対照的に不要なハッシュを回避する。そのようにしても、可変長データの前に(hash_typespend_typeに暗黙的に含まれる)データの長さにコミットすることで衝突は不可能になる。
  14. トランザクションダイジェストがscriptPubKeyにコミットするのはなぜ? これにより、実際に実行されたスクリプトが正しい場合でも(BIP 143のscriptCode)、使用されるアウトプットのタイプについてオフライン署名デバイスに嘘を付くのを防ぐ。scriptPubKeyにコミットしないと、攻撃者はオフライン署名デバイスに、実際にはSegwitのネイティブアウトプットにも関わらず、P2SHでラップされたSegwitアウトプットに署名するよう要求することで、デバイスに多めに手数料を支払わせるように騙すことができる。
  15. トランザクションダイジェストがすべてのインプットの量にコミットするのはなぜ? これにより取引の手数料についてオフラインの署名デバイスに嘘を付く可能性がなくなる。
  16. SIGHASH_SINGLEまたはSIGHASH_NONEがセットされている場合、トランザクションダイジェストがすべてのインプットのnSequenceにコミットするのはなぜ? これらをセットすると、すでにすべてのトランザクションインプットのprevoutにダイジェストがコミットされるため、nSequenceを別の値にすると役に立たなくなる。さらにこの変更により、nSequenceSIGHASH_SINGLESIGHASH_NONEトランザクションアウトプットに対してのみダイジェストの変更を許可し、インプットに関してはダイジェストを変更できないという観点と一致する。

後方互換

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

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

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があれば、その資金は回収できない。