Tapscript: オペコード追加と一部制限撤廃によるコベナンツの実現
Liquid Network Blockstream Research

Tapscript: オペコード追加と一部制限撤廃によるコベナンツの実現

Andrew Poelstra

概要

本稿はLiquid上でのTaprootローンチがもたらした改善点に関する3部作の最終章です。前回、前々回は、TaprootそのものPartially Signed Elements Transactionsについて解説しました。今回はElements Scriptで利用できるようになった新しいオペコードを説明した後、想定用途および利用例を紹介します。

今回のオペコード一式の追加は、2015年のElements Alphaローンチ以来となるScriptの大幅更新です。Elements Script以外にも、弊社のリサーチチームはビットコイン関連の暗号技術の革新、MuSig、Elementsブロックチェーン、ライトニングネットワーク、そしてスクリプト記述方法としてのSimplicityなど、さまざまな分野で研究開発に貢献しています。中でも、ビットコインへのTaproot導入には多くのリソースを投入しました。今回のElements Scriptの改善にも、Taproot由来の機能が複数含まれています:

  • オペコード数やスクリプト長の固定上限値の撤廃
  • Schnorr署名によるECDSA署名の置き換え
  • CHECKMULTISIGから、複数署名をまとめて検証可能なCHECKSIGADDへの変更
  • 従来のNOPオペコードと比べて、今後のソフトフォークの柔軟性を格段に高める多くのOP_SUCCESSオペコードの導入

また、Tapscript導入に際し、従来のコベナンツ実装方法に対して、開発者から多くのフィードバックが寄せられました。こうしたフィードバックを活用することで、今回のオペコード追加では、コベナンツの柔軟性、効率、使い勝手を向上することができました。コベナンツとは、資金送金にScriptで様々な条件を設定して制限を課し、「スマートコントラクト」を構築するものです。ビットコインへのコベナンツ導入に関しては、以前から多様な提案がなされてきましたが、今回はElementsに公開当初から実装されていたものを改善しました。

最後に、ビットコイン向けに開発されたMiniscript言語を拡張したElements Miniscriptについて簡単に説明します。Elements Miniscriptは、今回追加されたオペコードを用いたコベナンツを含むスクリプトを記述するための高級言語で、現在、急ピッチで開発が進められています。Elements MiniscriptはLiquid上における新たなアプリケーション開発を促進することが期待されます。このテーマについては、改めて別の機会にお届けします。

コベナンツとアセットのシナジー

ビットコインへのコベナンツ導入を初めて提案したのはGreg Maxwell氏で、2013年のことでした。Maxwell氏は半ば冗談でコベナンツの馬鹿げた利用法を募りました。笑えるコベナンツ用途を競って提案するのは楽しかったかもしれませんが、残念ながら、この悪ふざけが今だにコベナンツに関する議論に悪影響を及ぼしています。コベナンツの導入は賛否が分かれるテーマです。導入反対派はリスクが大きいと指摘しますが、これらのリスクは実用上は問題とならなかったり、コベナンツに限った問題ではありません。こうしたリスク評価についても、まだ着地点はなく、議論が続いています。

ここ最近で最も支持されたコベナンツ導入提案にOP_CTVがあります。OP_CTVは反対派の警戒心を解くため、あえて機能を限定するアプローチを採っています。

コベナンツ反対派はリスクを過大評価していると私たちは考えています。例えば、彼らはコベナンツを利用した資産凍結を警戒しています。政府がビットコイン送金に当局の許可(署名)を義務付け、コベナンツを使わないビットコインは「汚れた」コインとして送金が制限されることを恐れています。しかし、コベナンツ未導入の現在も、2-of-2 CHECKMULTISIGスクリプトを用いれば、同様のスキームは実現できます。当局は「事実上のコベナンツ」を持たないトランザクションに署名拒否すればよいのです。技術的には実現可能であるにも関わらず、スキームは現存しません。なぜなら、自由に送金できないリスクのあるコインは、利用者にも、顧客に代わってビットコインを管理する保管機関にも受け入れられないからです。また、スクリプトで制限されたコインの送金は、データ量が増えるため手数料が高くなる上、ウォレット側のソフトウェア対応も必要です。

同じことをコベナンツを導入して実装すると、手数料はさらに上昇し、ウォレットに求められる対応もより複雑になります。利用者にも開発者にも受け入れられないでしょう。

コベナンツのリスクが懸念されるほど高くないことを示す根拠はもう1つあります。Liquid Networkには、2018年のローンチ当時からコベナンツが実装されていますが、これまでコベナンツがウイルスのように拡散してネットワーク利用に支障をきたしたことは一度もありません。(ただし、後述しますが、Liquidでコベナンツ利用が広がっていない技術的な理由がいくつかあります。)

ビットコインへのコベナンツ導入は、リスクが限定的な一方、ValutsやVelocity Limitsなどの新機能の提供を可能にし、ビットコインを自己管理するためのオプションが増えるなど明確なメリットがあります。

また、アセット発行機能を備えるElementsでは、コベナンツの用途はさらに広がります。例えば:

  • 無期限の指値注文やアルゴリズム取引の作成
  • オプションなど金融デリバティブ商品の作成
  • 他コントラクトの認証手段となるNFT「コントロールトークン」の作成

複数のアセットが扱え、計算用オペコードが豊富なElementsでコベナンツを使えるようになれば、チューリング完全性に起因するセキュリティ分析のハードルを上げずに、イーサリアムやその競合チェーンと遜色のない機能を提供できると期待されます。ただし、現実はそれほど単純ではありません。

ElementsとBitcoin Scriptの関係

Elementsは2015年にBitcoinのフォークとして開発され、Elements Alphaというテストネットがローンチされました。2015年といえば、TaprootどころかSegwitの導入前で、Elements Alphaにはアセット発行機能もありませんでした。当時のTwo-way Pegの仕組みは必要以上に複雑で、バグを生みやすかったため、Liquidでの採用を前に設計が変更されました。ローンチ当初のElements Alphaの主機能は秘匿トランザクションで、これは後にネットワーク上で発行できるようになったアセットにも適用できます。この秘匿機能は署名データの隔離スキームの中では古いもので、後に主要部分は単純化されてSegwitとしてBitcoinに実装、残りはコベナンツを有効化する追加のオペコードとして整理されました。本稿で説明するのは後者です。

ElementsとLiquidのオペコード公開当時、Bitcoin Scriptは開発者が低級言語を手動で記述しており、形式手法による数学的解析も困難でした。弊社が開発した追加のオペコードは、Script言語としての機能性は改善しましたが、こうした問題は解決しませんでした。それでもLiquid上のレンディングなど高機能なアプリケーションが開発されました。これはひとえにアプリ開発者の努力と熱意の賜物で、弊社プラットフォームでの開発が容易だったわけではありません。実際、Bitmatrixなどのアプリケーションをデプロイするには、後述するスクリプト機能の更新が必要でした。問題解決の突破口は、弊社リサーチチームがElementsとは別に開発したMiniscriptによって、Bitcoin Scriptの形式的解析が可能になったことです。

Miniscriptを利用すれば、スクリプト内のすべての鍵を列挙し、どの組み合わせでトランザクションが作成できるか分析したり、使用条件によって変わるトランザクションサイズの上限を求めたり、送金実行のために満たすべき条件を文章化して問い合わせたり、スクリプトや署名の具体的なエンコード内容を計算できます。さらにMiniscriptは、弊社リサーチチームのAndrew ChowがPeter Wuilleのコンセプトを基に開発したPSBT(Partially Signed Bitcoin Transactions)との相性も抜群です。PSBTはトランザクションの生成に必要な各種データを組み合わせるためのプロトコルで、ウォレットは単にトランザクションの内容に問題がないことを確認後、ECDSAかSchnorrで署名さえすれば送金が可能です。ウォレットがスクリプトの内容を理解できなくても、残りの処理をMiniscriptのツーリングに委ねることができます。

しかし、Miniscriptの誕生をもってしても、Elementsでコベナンツを「簡単に」使えるようにする方法の突破口は見つかりませんでした。2016年頃に私達が実践していたコベナンツの作り方は、汎用的とは言い難い上、データサイズも大きく、Bitcoinから受け継いだ最大201個というオペコード数の上限を超えるケースが頻発する問題も抱えていました。Miniscript、PSBT、(Miniscriptを想定して設計された)Tapscriptによって、Bitcoin Scriptはパワフルに進化したにも関わらず、Elementsのコベナンツはこれらの恩恵を受けられませんでした。

斬新な抽象化がブレイクスルーに

2021年2月、ついに弊社のリサーチャーAndrew PoelstraとSanket Kanjalkarが、MiniscriptからElementsのコベナンツを扱う方法を見つけました。CHECKSIGFROMSTACK (CSFS) オペコードを用いて、全トランザクションデータを抽出してスタックの既定場所に置くだけの固定長のScriptテンプレート「マシーン」を用意し、コベナンツを定義する実際のスクリプトはマシーンに埋め込み、マシーンが抽出したデータを読み込むという方法です。従来のCSFSベースのコベナンツは、場合分けが多いためにデータ量が増しますが、マシーンを介すことで煩雑なコードを様々な条件で共有できるようになりました。

実際に採用したのは、上記アプローチとは別のものでしたが、この方法を検討する中で、汎用的なコベナンツを実装する上でのElements Scriptの弱点を解明できました:

  • Bitcoinから受け継いだ32ビットのビッグエンディアンを前提とした計算機能と、64ビットのリトルエンディアンで表現されるトランザクション金額の相性が悪い。
  • 掛け算や割り算のオペコードがなく、Elementsではシフト演算やいくつかの計算用オペコードを追加したものの、手数料計算に必要な基本的なオペコードを欠く。
  • 手数料計算に関して、CSFSを使ったコベナンツはトランザクションのウェイト(手数料計算上のサイズ)を読み取れず、手数料率を計算できない。
  • CSFSはトランザクションデータのハッシュ値計算を要するが、スタック上のエレメントの520バイト制限と柔軟性を欠くSHA256オペコードの制限があるため、520バイト以上のデータについてはハッシュ値を計算できない。CSFSベースのコベナンツに対応したいウォレット開発者にとって、このトランザクションデータのサイズ制約は厄介な問題となっている。
  • CSFSの仕組みに数十個、64ビット整数と32ビット整数の相互変換にも数十個のオペコードが求められる一方で、スクリプト全体では201個という実行数の上限がある。
  • 楕円曲線暗号に関する計算は、工夫次第で多くのことができるにも関わらず、実質的に署名検証に限られ、Taprootアウトプットが正しく構成されているかは検証できない。この制限をTapscriptに引き継いでいたら、スクリプトは複雑性を増す一方で機能性は劣っていた。

こうしたElements Scriptの弱点をしっかりと認識できたことで、以下の対策を講じることができました:

  • 掛け算など64ビットの値を扱うオペコードを追加し、既存のオペコードと併用できるように、従来の32ビットの値との変換用のオペコードを追加した。
  • トランザクションデータを抽出するための”Direct Introspection"オペコードを追加することで「マシーン」を不要とし、データ参照に必要なオペコード数を圧縮。TXWEIGHTオペコードの追加によって、手数料率計算に必要なトランザクションウェイトを取得可能にした。
  • ”Streaming hash"オペコードを追加することで、スタックを複数要素に分けて保存したデータをハッシュできるようになり、520バイトのスタック要素のサイズ制限はそのままで、インタプリタのリソース使用の増加も回避できた。
  • ECMULSCALARVERIFYオペコードの追加でTaprootアウトプットを扱えるようにし、Pay-to-Contractコミットメントや他の楕円曲線暗号関連の機能を実現した。
  • 201個というオペコード数の上限がBitcoinで撤廃されたのを受けて、同制限を撤廃。ただ、上記の変更の結果、撤廃する必要性は薄れていたかもしれない。

こうした変更を加えたことで、Bitcoin Scriptモデル(ディスクアクセスなし、リソース消費の制限、ループなど複雑な制御フローなし)を継承つつ、少数のオペコードで高度なアプリケーションにも使えるコベナンツをElementsで利用できるようになりました。

新しいオペコードの一覧表はこちら

従来の課題を克服できたので、今後はElementsに限らず、コベナンツに関する根本的な問題の解決にフォーカスできます。新しく追加したオペコードの使用例として、直近60ブロック以内に送金できる金額をMAX_WITHDRAWに限定するVaultスキームを考えてみましょう。

これをコベナンツで表現するには、次の条件を満たす必要があります(使用しているMiniscriptの記法については別の機会に改めて説明します):

  • Partial Withdrawal(partial_withdraw): コベナンツにMAX_WITHDRAWを超える金額が含まれていれば、送金後のコベナンツ残高の最小値はtotal_value - MAX_WITHDRAWとなる
  • "num64_gt(curr_inp_v,MAX_WITHDRAW)": 入力の合計金額がMAX_SATSを超過しているか
  • "asset_eq(curr_inp_asset,out_asset(0))": 入力アセットと出力アセット(0)が同一か
  • "num64_geq(out_v(0),sub64(curr_inp_v,MAX_WITHDRAW))": 出力先の金額がtotal_value - MAX_WITHDRAW以上か(そうである必要がある)
  • "spk_eq(curr_inp_spk,out_spk(0))": 残ったコインはコベナンツに再送される
  • Full Withdrawal(full_withdraw): コベナンツの残高がMAX_WITHDRAW以下なら送金先に制限は課さない
  • num64_leq(curr_inp_v,MAX_WITHDRAW)
  • Key Control(keys): 送金条件として、次回の送金処理までに60ブロックの経過と、秘密鍵Kによる署名を設定する
  • pk(K): 秘密鍵Kによる署名を求める
  • older(60): 次回送金までに60ブロックの経過を求める
  • curr_idx_eq(0): コベナンツUTXOを同一トランザクションで2度使用した際、片方がコベナンツの制限を無視できてしまう「ハーフスペンド問題」対策として、使用するコベナンツのSpending Indexは0でなければならない
  • 最後に、上記の条件をすべて組み合わせて表現する: and(keys,or(full_withdraw,partial_withdraw))

このスキームに非常時用の秘密鍵、マルチステップ送金、ホワイトリスト、マルチシグなどを組み合わせて拡張することも可能です。

未解決課題と今後の展望

コベナンツに対応するようMiniscriptを拡張するとともに、コベナンツを効率的に実現できるようElements Scriptにオペコードを追加することで、ユーザーが簡単にコベナンツのスクリプトを記述でき、その挙動を理解した上でコントラクトの実行に必要なトランザクションを生成するためのフレームワークを作ることができました。しかし、それと引き換えに、Miniscriptの再帰的な構造が使えなくなり、コントラクト解析方法が一部利用できなくなりました。

例えば、Miniscriptではand_bノードを作成して、子ノードとして2つのスクリプトを含めることで、2つのMiniscriptを合成できました。(Scriptとしては、単純に別々のスクリプトをつなげて最後にBOOLANDオペコードを加えただけです。)ユーザーが両方のスクリプトの条件を満たせるなら、当然それらを合成したスクリプトの条件も満たせるはずです。

複雑なスクリプトの実行可能性は、スクリプトを再帰的に分解していくことで判断できます。Andノードを見つけるたびに、そのサブスクリプトの両方、Orノードを見つけるたびに、そのサブスクリプトの一方を実行できる必要があります。

残念ながら、コベナンツが絡むと、この方法で2つのスクリプトを合成することはできません。文法的にはand_bでコベナンツを含む2スクリプトをつなぐことはできますが、両方のサブスクリプトの条件を満たしても、合成スクリプトが実行できない場合も発生します。例えば、トランザクション内の最初のアウトプットで、アセットAをバーンする条件が定められたコベナンツと、最初のアウトプットでアセットBをバーンする条件が定められたコベナンツがあるとします。当然ながら、単独では両方の条件を満たすことができても、両方同時には満たせません。

コベナンツは作成中のトランザクションにグローバルな制約を課すため、個々のスクリプト文が他のスクリプト文に影響を及ぼすことがあります。言い換えると、Bitcoin Miniscriptにおいて、スクリプトは使用条件を表す単調なブール関数と捉えることができます。スクリプトは、どの条件(署名、ハッシュ値のプリイメージ等)を満たしたら、コインを送金できるか、できないかを規定します。これらの規定が単調であるということは、ある条件の集合が満たされる場合、その任意の上位集合も満たされることになります。

しかし、コベナンツが絡むと、この単調なブール関数に例えられるモデルは当てはまらず、Miniscriptが提供する汎用的で強力な解析機能が失われてしまいます。コベナンツを利用するElements Miniscriptプログラムは、具体的な質問と回答というプロセスの個別解析を要します。

残念ながら、弊社が並行開発しているScriptを完全に置き換えるSimplicityも、同じ問題を抱えます。ただ、Simplicityのインタプリタには、Coq定理証明アシスタントのリファレンス実装が同梱されており、人間が「正しい質問」を選ぶ必要があるものの、回答は機械検証が可能で、個別コントラクトの解析に役立ちます。一方、Elements Miniscriptには、人間が地道にコードレビューする以外、プログラムの検証手段がありません。高機能なプログラムもシンプルに実装できるよう設計したので、検証が容易かつ正確に行えることを願うばかりです。

将来的には、Elements Miniscriptの機能を拡充し、コベナンツをウォレットなどで広く利用できるようツーリングやライブラリを充実させ、今回拡張されたElements Scriptよりも更に柔軟性の高いスクリプトが実現できるようSimplicityの開発を進めていく計画です。

ハッピー・ハッキング!

If you have specific preferences, please, mark the topic(s) you would like to read: