最近、Anthony TownsによってCovenantsの新しい実装方式を提案されている↓
- https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019419.html
- https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-September/019420.html
これまでの提案
Covenantsとは、コインの使用方法に制約を加えるコントラクトの一種で、2016年からこれまで以下のような方式が提案されてる:
- Emin Gun SirerらによるBitcoin Covenants
(新しいopcode OP_CHECKOUTPUTVERIFYを導入する方式) - ElementsのOP_CHECKSIGFROMSTACKとOP_CATを使った方式
- Jeremy RubinによるOP_CHECKTEMPLATEVERIFYを使った方式(BIP-119)
いずれも、新しいopcodeの導入を必要とするため、既存のBitcoinではまだ利用できない。
TaprootとOP_TLUVを利用した方式
今回提案されているのは、Taprootのスクリプトツリーと新しい2つのopcodeによりCovenantsを実現する新しい方式。
TaprootとTapscriptの基本的な構成
プロトコルを理解するためには、まずTaprootやTapscriptに関する前提知識が必要だが、簡略化すると以下のようにロックスクリプトを構成する(詳細はGBEC動画を参照:Taproot、Tapscript)。
Taprootのロックスクリプトの主体は、内部公開鍵(P)とスクリプトで構成されるマークルツリーのルートハッシュ(S)から計算した
Q = P + H(P || S)G
になる。例えばスクリプトの条件が5つ(A, B, C, D, E)ある場合、以下のようなツリーとSの値になる。
S = H_taptweak(P || ABCDE) | H_tapbranch(ABCDE) / \ / H_tapbranch(CDE) / / \ H_tapbranch(AB) H_tapbranch(CD) H_tapleaf(E) / \ / \ H_tapleaf(A) H_tapleaf(B) H_tapleaf(C) H_tapleaf(D)
Qにロックされているコインを条件Eのスクリプトを使ってアンロックする場合、トランザクションインプットのwitnessに以下のデータを提供する:
TLUV
Covenantsを実現するにあたっては、TAPLEAF_UPDATE_VERIFY
とIN_OUT_AMOUNT
という2つの新しいopcodeを導入する。
TAPLEAF_UPDATE_VERIFY
TAPLEAF_UPDATE_VERIFY
(TLUV)は、使用するインプットに対応する(同じインデックスの)アウトプットに対して、適用するscriptPubkeyを強制するopcodeになる。
つまりアウトプットのscriptPubkeyがインプットのscriptPubkeyと同じか、または条件をドロップしたり、追加したりしたスクリプトツリーで構成されるscriptPubkeyと一致するかを検証する。
例えば、↑のようなA, B, C, D, Eという5つの条件を持つスクリプトがあり、Eの条件を使ってインプットをアンロックする場合、
- それと同じスクリプトツリー(A, B, C, D, E)を持つscriptPubkeyであるか?または、
- インプットのスクリプトツリーから、現在実行中のスクリプト(↑の場合E)を削除したスクリプトツリー(A, B, C, D)を持つscriptPubkeyであるか?または、
- さらに、新しいスクリプト条件(F)を追加したスクリプトツリー(A, B, C, D, E, F)を持つscriptPubkeyであるか?
といったこを検証可能にする。
TLUV opcodeは入力として以下の3つの要素をスタック上から取得する↓
- 内部公開鍵の更新方法を指定する値
(加算(減算)する公開鍵を指定:例えば↑のPからP' = P + Xに更新する場合はXを指定) - マークルパスの新しい条件を指定する値
- 現在実行中のスクリプトをツリーから削除する、および/または削除するマークルパスを指定する値
例えば、0 0 0 TLUV
というスクリプトの場合、インプットと全く同じscriptPubkeyをアウトプットに強制する。
0 F 0 TLUV
というスクリプトの場合、新しいスクリプト条件Fを↑のスクリプトツリーに追加したscriptPubkeyを強制する。
<X> 0 0 TLUV
というスクリプトの場合、P + Xを新しい内部公開鍵としてスクリプトツリーは同じままののscriptPubkeyを強制する。このときXは32バイトのx-only public key。
0 F 2 TLUV
というスクリプトの場合、現在実行中のスクリプトを削除し、新しいスクリプト条件Fをスクリプトツリーに追加したscriptPubkeyを強制する。ここで2
の値は↑の3つめの制御用の数値で、この値の各ビット値は以下を示す:
- 最下位ビットはx-only public keyであるXのy座標に関するパリティビット
(y座標が偶数の場合は0、奇数の場合は1) - 次のビットは、現在のスクリプトをマークルパスからドロップするかどうかを示す値で、0であれば現在のスクリプトを維持し、1であれば現在のスクリプトをツリーから削除する。
- それ以降のビット値は、ツリーから削除するマークルパスのステップ数。
2 = 10
の場合、Xは今回未指定なので最下位ビット関係ないとして、次のビット値が1なので現在実行中のスクリプト(E)をツリーから削除する。
例えば、0 0 4 TLUV
というスクリプトの場合、マークルパスの最後のステップ(CD)を削除した(Eの兄弟ノードを削除した)スクリプトツリーのscriptPubkeyを強制する。
IN_OUT_AMOUNT
2つめのIN_OUT_AMOUNT
opcodeは、インプットのコインの量と対応するアウトプットのコインの量をスタックにプッシュする新しいopcode。
このopcodeをTLUVと他の算術演算のopcodeを組み合わせて使用することで、ルールを強制するアウトプットに対して適切なコイン量が保持されているかどうかを強制できる。
TLUVのユースケース
1つは、当初Covenantsが提案されていた頃からのユースケースだが、Vaultを実現することができる。また、その他に、単一のUTXOでマルチーパーティの資金を管理するCoinPoolを実現するための利用も期待されている。
所感
↑がTLUVを使った新しいCovenantsの実現方式。
IN_OUT_AMOUNT
で数値演算する際に、現状のopcodeは32 bitの演算しかできないのをどうするか?- 内部公開鍵の更新にあたって、y座標が奇数になった場合にどうするか?
など課題はあるものの、Taprootのスクリプトツリーを使った条件の追加/削除をする構成はおもしろいアプローチだと思う。メーリングリストの投稿にも記載されてけど、現状は条件を予めすべて固定しておかなければならないが、OP_CATなどが有効になればよりダイナミックなCovenantsの構築も見えてくる。