MATT(Merkleize All The Things!)は、BitcoinのようなUTXOモデルで汎用的なスマートコントラクトを構築しようという提案の1つ↓
Taprootを利用したステートへのコミット
EVMのようなステートツリーを持たないBitcoinで唯一管理されるステートはUTXOセットのみ。汎用的なスマートコントラクトを実現する場合、コントラクトのステートを管理する必要がある。
MATTでは、コントラクトのステートをマークルツリーで表現し、そのコミットメントをTaprootのアウトプット(P2TR)に含めるようになっている。(EVMノードがフルステートを管理しなければならないのに対し、MATTの場合ステートへのコミットメントのみが間接的にオンチェーンで管理されることになる。)
ステートのマークル化
コントラクトの各ステートは、マークルツリーのリーフノードに割り当てられ、以下のようにステートツリーを構成し、そのマークルルート(ステートルート)を計算する。
ステートへのコミット
ステートをマークル化したら、そのマークルルートを使ってTaprootの内部公開鍵を調整する(Taprootの仕組みについては過去の記事かGBEC動画参照)。
通常のTaprootの内部公開鍵をQとした場合、
Q' = Q + H(Q || <↑のステートルート>)G
という形で調整し、Q'をTaprootの内部公開鍵とすることで、ステートツリーにコミットしたP2TRを作成することができる。
状態遷移ルールを強制するためのコベナンツ
スマートコントラクトの状態遷移は、コントラクトを持つUTXOを使用して、コントラクトのルールに合致する新しいUTXOを生成することで行われる。このとき、ルールを強制するために必要なのがコベナンツ。コベナンツには、いろんな実装方法が提案されているけど、まだBitcoinには実装されていない(これまでの提案内容についてはこちら)。ただ、汎用的なスマートコントラクトを作ろうとすると必要になる機能。
MATTでもコベナンツが必要で、OP_CHECKCONTRACTVERIFY
opcodeの導入を提案してい(もともとはOP_CHECKINPUTCOVENANTVERIFY
とOP_CHECKOUTPUTCOVENANTVERIFY
として提案されてたもので、現在はまとめてOP_CHECKCONTRACTVERIFY
になっている。ただまだ仕様策定中なので、もろもろ変わる可能性あり) 。
OP_CHECKCONTRACTVERIFY
は、スタックから、data
、index
、pk
、taptree
、flags
を取得し、トランザクション内のindex
が参照する対象のscriptPubkey(インプット/アウトプット)がpk
およびdata
、taptree
で調整された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つの支払先の公開鍵と金額をそれぞれとする。そして、この公開鍵と金額を連結した値をリーフノードとしたマークルツリー(ステートツリー)を作成する。
このステートルートを使って内部公開鍵を調整し導出し、適用するスクリプトをTapscriptとして、P2TRアウトプットの公開鍵を導出する。
↑のUTXOをアンロックし、決められた2つの支払先に支払いをする際のインプットのwitnessをスタックに入れるとスタックの初期状態は以下のようになる。
この初期状態で↑のスクリプトを実行する↓
OP_DUP OP_TOALTSTACK
でスタック最上位のステートルートがコピーされ、アルトスタックに移動。OP_CHECKINPUTCONTRACT
*1で、スタックからステートルートと内部公開鍵を取得し、その調整値がインプットのP2TRと一致するか検証する。OP_2DROP
で、スタックからステートルートと内部公開鍵を削除。OP_2DUP OP_CAT OP_SHA256 OP_TOALTSTACK
で、をコピーし、連結し、ハッシュしてアルトスタックへ移動*2。OP_SWAP OP_DROP
で、メインスタック上のを削除00 00 00 OP_CHECKOUTPUTCONTRACT
で、スタック上の公開鍵が0番目のアウトプットのP2TRアウトプットの公開鍵と一致するか検証。- ↑の4〜6と同じことをに対しても実行。
- この時点でアルトスタックには、ととステートルートが残っているので、
OP_FROMALTSTACK OP_FROMALTSTACK
で、とをメインスタックに移動。 OP_CAT OP_SHA256
で、とを連結し、ハッシュ値を計算。OP_FROMALTSTACK
でアルトスタックに残っているステートルートをメインスタックに移動。OP_EQUAL
で9と10が一致するか検証
これにより、使用されるUTXOが決められた支払先に送信されることを検証できる。
インプット/アウトプットのP2TRにステートという概念を導入し、それをTapscriptを使って任意の処理して検証する仕組みを作るというアプローチはこれまでのコベナンツの提案と違ってて興味深い。
計算のマークル化
MATTでは、コントラクトによるに任意の計算をオフチェーンで実行し、Fraud Proofベースの調停システムを用いてスケーラビリティを担保する仕組みも考えられているみたい。これについては、まだよく理解してないので、記事の続きはまた後日。
ただ、任意の計算を単純なステップに分解し、分解した各ステップの計算と状態からマークルツリーを構成し、計算自体もマークル化しようとしており、
- ステートのマークル化
- スクリプトのマークル化
- 計算のマークル化
と、いろんなものをマークル化してるので、Merkleize All The Things!ということらしい。