Develop with pleasure!

福岡でCloudとかBlockchainとか。

Bitcoinで汎用的なスマートコントラクトを実現するための提案MATT

MATT(Merkleize All The Things!)は、BitcoinのようなUTXOモデルで汎用的なスマートコントラクトを構築しようという提案の1つ↓

https://merkle.fun/

Taprootを利用したステートへのコミット

EVMのようなステートツリーを持たないBitcoinで唯一管理されるステートはUTXOセットのみ。汎用的なスマートコントラクトを実現する場合、コントラクトのステートを管理する必要がある。

MATTでは、コントラクトのステートをマークルツリーで表現し、そのコミットメントをTaprootのアウトプット(P2TR)に含めるようになっている。(EVMノードがフルステートを管理しなければならないのに対し、MATTの場合ステートへのコミットメントのみが間接的にオンチェーンで管理されることになる。)

ステートのマークル化

コントラクトの各ステートは、マークルツリーのリーフノードに割り当てられ、以下のようにステートツリーを構成し、そのマークルルート(ステートルート)を計算する。

https://merkle.fun/assets/merkletree.png

ステートへのコミット

ステートをマークル化したら、そのマークルルートを使ってTaprootの内部公開鍵を調整する(Taprootの仕組みについては過去の記事GBEC動画参照)。

通常のTaprootの内部公開鍵をQとした場合、

Q' = Q + H(Q || <↑のステートルート>)G

という形で調整し、Q'をTaprootの内部公開鍵とすることで、ステートツリーにコミットしたP2TRを作成することができる。

状態遷移ルールを強制するためのコベナンツ

スマートコントラクトの状態遷移は、コントラクトを持つUTXOを使用して、コントラクトのルールに合致する新しいUTXOを生成することで行われる。このとき、ルールを強制するために必要なのがコベナンツコベナンツには、いろんな実装方法が提案されているけど、まだBitcoinには実装されていない(これまでの提案内容についてはこちら)。ただ、汎用的なスマートコントラクトを作ろうとすると必要になる機能。

MATTでもコベナンツが必要で、OP_CHECKCONTRACTVERIFY opcodeの導入を提案してい(もともとはOP_CHECKINPUTCOVENANTVERIFYOP_CHECKOUTPUTCOVENANTVERIFYとして提案されてたもので、現在はまとめてOP_CHECKCONTRACTVERIFYになっている。ただまだ仕様策定中なので、もろもろ変わる可能性あり) 。

OP_CHECKCONTRACTVERIFYは、スタックから、dataindexpktaptreeflagsを取得し、トランザクション内のindexが参照する対象のscriptPubkey(インプット/アウトプット)がpkおよびdatataptreeで調整されたP2TRであることを検証するopcode。

使い方は、

  • コントラクトのロジックを、P2TR内のスクリプトツリー(Tapscript)に記述し、
  • 現在のステート(↑のステートルート)を利用して、スタック上でコントラクトを実行し、新しいステートに更新し、
  • 更新したステートツリーのルートが新しいP2TRアウトプットのステートと一致する

といった検証をするような感じっぽい。

MATTを使ったCTVの実現

もう少し具体的に実装例を見てみる。こちらのリポジトリで、MATTを使ったいくつかのデモが提供されてる。そのうち、コベナンツの提案の1つであるOP_CTVができることをMATTでやったらどうなるかというのが↓

https://github.com/halseth/tapsim/tree/matt-demo/examples/matt/ctv

これは、単純に静的な2つの支払先の公開鍵と金額が決まっているUTXOをMATTで作る例(あまり汎用的ではないけどopcodeの処理を確認するには簡単な方が良い)。

2つの支払先の公開鍵と金額をそれぞれ {(P_1, Amount_1), (P_2, Amount_2)}とする。そして、この公開鍵と金額を連結した値をリーフノードとしたマークルツリー(ステートツリー)を作成する。

このステートルートを使って内部公開鍵を調整し導出し、適用するスクリプトをTapscriptとして、P2TRアウトプットの公開鍵を導出する。

↑のUTXOをアンロックし、決められた2つの支払先に支払いをする際のインプットのwitnessをスタックに入れるとスタックの初期状態は以下のようになる。

この初期状態で↑のスクリプトを実行する↓

  1. OP_DUP OP_TOALTSTACKでスタック最上位のステートルートがコピーされ、アルトスタックに移動。
  2. OP_CHECKINPUTCONTRACT*1で、スタックからステートルートと内部公開鍵を取得し、その調整値がインプットのP2TRと一致するか検証する。
  3. OP_2DROPで、スタックからステートルートと内部公開鍵を削除。
  4. OP_2DUP OP_CAT OP_SHA256 OP_TOALTSTACKで、 {P_1, Amount_1}をコピーし、連結し、ハッシュしてアルトスタックへ移動*2
  5. OP_SWAP OP_DROPで、メインスタック上の {Amount_1}を削除
  6. 00 00 00 OP_CHECKOUTPUTCONTRACTで、スタック上の公開鍵が0番目のアウトプットのP2TRアウトプットの公開鍵と一致するか検証。
  7. ↑の4〜6と同じことを {P_2, Amount_2}に対しても実行。
  8. この時点でアルトスタックには、 {H(P_1 || Amount_1)} {H(P_2 || Amount_2)}とステートルートが残っているので、OP_FROMALTSTACK OP_FROMALTSTACKで、 {H(P_1 || Amount_1)} {H(P_2 || Amount_2)}をメインスタックに移動。
  9. OP_CAT OP_SHA256で、 {H(P_1 || Amount_1)} {H(P_2 || Amount_2)}を連結し、ハッシュ値を計算。
  10. OP_FROMALTSTACKでアルトスタックに残っているステートルートをメインスタックに移動。
  11. OP_EQUALで9と10が一致するか検証

これにより、使用されるUTXOが決められた支払先に送信されることを検証できる。

インプット/アウトプットのP2TRにステートという概念を導入し、それをTapscriptを使って任意の処理して検証する仕組みを作るというアプローチはこれまでのコベナンツの提案と違ってて興味深い。

計算のマークル化

MATTでは、コントラクトによるに任意の計算をオフチェーンで実行し、Fraud Proofベースの調停システムを用いてスケーラビリティを担保する仕組みも考えられているみたい。これについては、まだよく理解してないので、記事の続きはまた後日。

ただ、任意の計算を単純なステップに分解し、分解した各ステップの計算と状態からマークルツリーを構成し、計算自体もマークル化しようとしており、

  • ステートのマークル化
  • スクリプトのマークル化
  • 計算のマークル化

と、いろんなものをマークル化してるので、Merkleize All The Things!ということらしい。

*1:OP_CHECKCONTRACTVERIFYへの更新前のものでインプット/アウトプット毎にopcodeが分離してるバージョン

*2:OP_CATも現状のBitcoinでは無効化されているので有効化が必要