Develop with pleasure!

福岡でCloudとかBlockchainとか。

Tapscript用の新しいopcode OP_INTERNALKEYを提案するBIP-349

先日、TapscriptにOP_INTERNALKEYという新しいopcodeの導入を提案するBIP-349がBIPとして登録された↑

https://github.com/bitcoin/bips/blob/master/bip-0349.md

OP_INTERNALKEY

OP_INTERNALKEY opcodeは、スタックにTaprootの内部鍵をプッシュするopcode。Tapscriptには将来のソフトフォークで拡張可能なOP_SUCCESS系のopcodeがいくつか確保されており、その内のOP_SUCCESS203(0xcb)をOP_INTERNALKEYに割り当てる。

Taprootについておさらい

Taprootの内部鍵について簡単に説明すると、Taprootのアウトプットは、よくある公開鍵宛の支払い(P2PKHとかP2WPKH)とスクリプトを用いた条件付きの支払い(P2SHとかP2WSH)の両方を1つの形式のアウトプットで表現できるようになっている*1

  • この公開鍵部分を内部鍵(Internal Key)と呼ぶ。ここではPとする。
  • スクリプト部分は、以下のような手順で公開鍵としてエンコードされる
    1. まず、各アンロック条件をリーフノードとしたマークルツリー(スクリプトツリー)を構成し、
    2. マークルツリーのルートハッシュを計算する(ルートハッシュの値をrとする)
    3. 内部鍵とルートハッシュの値を連結したタグ付きハッシュ {t = H_{TapTweak}(P || r)}を計算する。
    4. 楕円曲線のベースポイントGにtを乗算して公開鍵tGを計算する。

こうしてできた、2つの楕円曲線上の点(公開鍵)を加算した点Q = P + tGをsegwit programとしたsegwit version 1のアウトプットがTaprootアウトプットになる。ロックスクリプトは↓

OP_1 <Q>

詳細については、GBEC動画や、過去のブログ記事を参照。

Taprootアウトプットの使用

上記のように構成されたTaprootのアウトプットを使用する場合は、

  • 鍵のみを使用してアンロックするkey-path
  • 鍵は使用せずにスクリプトを使用してアンロックをするscript-path

のいずれかでアンロックすることでTparootのUTXOを使用できる。

key-pathでのアンロック

この場合は、公開鍵Q(Pではない)に対応する秘密鍵を使用して有効なSchnorr署名を提供することでアンロックできる。内部鍵の作成者であればPの秘密鍵は知っているので(ここでは仮にxとする)、マークルルートから生成したtの値も知っていれば、Qの秘密鍵x + tを知ることができる。

Taproot UTXOを使用するトランザクションのwitnessとして提供するデータは、この秘密鍵で生成したSchnorr署名のみ。

script-pathでのアンロック

この場合はkey-pathと比べると少し複雑で、Taproot UTXOを使用するトランザクションのwitnessとして以下のデータを提供する必要がある。

  1. (当然ながら)使用するスクリプトをアンロックするのに必要なデータ
  2. スクリプトツリー内のアンロックに使用するスクリプト自体
  3. 2のスクリプトがツリー内に存在することを証明するマークルプルーフ
  4. Qを構成する際に使用した内部鍵P

3,4のデータはControl Blockという構造のデータの一部として提供される。これらのデータが提供されると、

  • スクリプトとマークルプルーフを使ってスクリプトツリーのルートハッシュが計算され
  • それとPから {t = H_{TapTweak}(P || r)}を計算し、
  • P + tGが使用するTaproot UTXOのQと等しいか検証できる。

検証をパスしたらスクリプトに対して1のデータを使って2のスクリプトのアンロックを試みる。

OP_INTERNALKEYの用途

本題に戻って、OP_INTERNALKEYの機能は、script-pathを使ってアンロックする際に、↑のようにwitnessとして提供された内部鍵Pをスタックにプッシュすること。

内部鍵Pは、witnessでControl Blockの一部として提供されるものの、上記の検証をする際にControl BlockのデータはスタックからPOPされてるので、検証をパスした後のスクリプト実行時にはスタック上に存在しない。

スクリプト側でOP_INTERNALKEYを使って内部鍵PをスタックにPUSHすることで、key-pathを構成する際に使用した鍵と同じ鍵を使った条件をスクリプトでも利用できるようになる。見方を変えると、本来はSchnorr署名のみを提供するkey-pathに対して、条件付きのkey-pathを構成することができる。

OP_INTERNALKEYを導入しなくても、内部鍵と同じ鍵をプッシュするようなスクリプトを最初から作っておけば同じことはできる。ただBIPでも言及されていたけど、OP_INTERNALKEYを使うと、スクリプトで同じことをする場合に比べて、8 vbyte分の節約になる。これは、スクリプトで内部鍵をプッシュする場合、内部鍵のデータ32 byte + データのプッシュopcode 1 byte分合計33 byte分のスペースを使用する。OP_INTERNALKEYの場合は、このopcode 1 byte分のみで済むため32 byte(=8 vbyte)分の節約になるということ。

LNhance

またOP_INTERNALKEYは、他の3つの新しいopcodeの追加と合わせて、ライトニングネットワークの機能強化や、Arkを含む他のUTXO共有プロトコルを有効にするための提案LNhanceの一部↓

*1:そのため、アウトプットだけみても公開鍵宛なのかスクリプト宛なのか区別がつかない