Bitcoin Cashに追加されたOP_CHECKDATASIGの紹介

コールドウォレット開発グループの原です。

一昨年Bitcoin Cash(BCH)のハードフォークでOP_CHECKDATASIGというOP_CODEが追加されました。 OP_CHECKDATASIGよってBCHを使ったオンチェーンアプリケーションの自由度は大幅に広がり、オンチェーンチェス*1など様々な提案や実装が生まれています。

今回はそのOP_CHECKDATASIGとその使い道について紹介します。

Bitcoin Scriptやトランザクションについての知識が必要なので、必要があれば下記の記事などを参照してください。 BCHのトランザクションはSegwitが導入されていないことを除き、基本的にはBitcoin Core(BTC)と同じ仕組みで処理されます。

OP_CHECKDATASIG

OP_CHECKDATASIG(OP_CDS)は、2018年11月15日のハードフォークでBCHに追加されたOP_CODEです。
署名、メッセージおよび公開鍵を受け取り、メッセージに対する署名を検証します。

通常、BitcoinではOP_CHECKSIGなどを使って署名の検証を行っていますが、それらはトランザクションに対する署名しか検証することができません。 しかし、OP_CDSが追加されたことにより、Bitcoin Scriptの中でトランザクション以外のデータに対して署名の検証ができるようになりました。

使い方

<sig> <msg> <pubKey> OP_CHECKDATASIG
  • <sig><msg><pubKey>で検証できたら、trueをスタックに追加します
  • <sig>が空だったら、falseをスタックに追加します
  • それ以外(パラメータが不足など)はスクリプトが失敗します

今回はOP_CDSの使い道の一つとして、Covenantsについて説明します。

Covenants

Covenantsとはトランザクションのアウトプットを検証することで、送金先や送金額などを自由に変更できないようにしたトランザクションのことを指します。 BCHではOP_CDSとOP_CHECKSIGを組み合わせることにより実現できます。

トランザクションの署名方法

この後の説明のため、まずBCHでのトランザクションの署名方法について説明します。

トランザクションを署名するには、まず下記のように仕様で定められたフォーマットでトランザクションをシリアライズします。

  1. nVersion of the transaction (4-byte little endian)
  2. hashPrevouts (32-byte hash)
  3. hashSequence (32-byte hash)
  4. outpoint (32-byte hash + 4-byte little endian)
  5. scriptCode of the input (serialized as scripts inside CTxOuts)
  6. value of the output spent by this input (8-byte little endian)
  7. nSequence of the input (4-byte little endian)
  8. hashOutputs (32-byte hash)
  9. nLocktime of the transaction (4-byte little endian)
  10. sighash type of the signature (4-byte little endian)

そして、シリアライズされたトランザクション(preimage)をSHA256でハッシュしたものに秘密鍵で署名をします。

Covenantsの仕組み

CovenantsはBitcoin Scriptを使って実装されます。 Bitcoin Scriptからはトランザクションのアウトプットを参照することができないため、アウトプットの情報を含んでいるpreimageをscriptSigで渡します。 そして、ここで渡すpreimageが本当にトランザクションから作られたものか検証するために、OP_CDSとOP_CHECKSIGを使います。

スクリプト

下記のようなscriptSigとredeem scriptで検証を行います。

  • scriptSigに必要なパラメータ
    • <pubKey> - 署名を検証するための公開鍵
    • <sig> - トランザクションの署名
    • <preimage> - シリアライズされたトランザクション
  • redeem scriptで検証する内容
    1. OP_CHECKSIGで<pubKey><sig>を使いトランザクションを検証します
    2. OP_CDSで<pubKey><sig>を使い<preimage>を検証します
      • 1.と同じ<pubKey><sig>を使って検証するため、<preimage>が本当にトランザクションから作られたことが確認できます
    3. <preimage>に含まれる送金先の情報が期待通りか検証します

このCovenantsの仕組みを応用したものとして、今回はLooping transactionSIGHASH_NOINPUT emulationを紹介します。

Looping transaction

Looping transactionは、トランザクションの送金先に同じscriptPubKeyが使われていることを強制するトランザクションです。 Bitcoin Scriptはループを表現する機能を提供していませんが、Looping transactionで代用できるケースもあります。

Looping transactionを利用したものとして、Last WillMecenasというウォレットのプラグインがあります。

スクリプト

scriptSigとredeem scriptは以下のようになります。

  • scriptSigに必要なパラメータ
    • <pubKey> - 署名を検証するための公開鍵
    • <sig> - トランザクションの署名
    • <preimage> - シリアライズされたトランザクション
  • redeem scriptで検証する内容
    1. OP_CHECKSIGで<pubKey><sig>を使いトランザクションを検証します
    2. OP_CDSで<pubKey><sig>を使い<preimage>を検証します
      • 1.と同じ<pubKey><sig>を使って検証するため、<preimage>がシリアライズされたトランザクションであることが確認できます
    3. <preimage>中の6. value of the output spent by this inputからトランザクションの送金額を計算する
    4. <preimage>中の5. scriptCode of the inputから自身のredeem scriptを取り出し、scriptPubKeyを生成する
    5. 送金額とscriptPubKeyを使って期待するアウトプットを組み立てる
    6. <preimage>中の8. hashOutputsが期待するアウトプットから作られたことを検証する

このスクリプトはBitcoin Scriptで実装すると複雑になるため、SpednなどのBitcoin Scriptにコンパイルされる高級言語で実装されることが多いです。 Spednについてはまた機会があれば紹介したいと思いますが、参考にSpednで実装された例を紹介します。

SIGHASH_NOINPUT emulation

SIGHASH_NOINPUTは、Eltooなどのオフチェーンプロトコルに必要なSIGHASHです。 このSIGHASHを使うと、preimagehashPrevoutsなどのインプットに関する情報を含めず構築できるようになり、preimageや署名を変更せずにトランザクションのインプットだけを変更できるようになります。

SIGHASH_NOINPUTはまだ実装されていないため使えませんが、OP_CDSを使って再現する方法が提案されています。

仕組み

まず、シリアライズフォーマット中の1-7をprefix、8-10をsuffixとして、preimageを分割します。 そして、トランザクションの所有者になる人(オーナー)はsuffixに署名をします。

suffixにインプットの情報が含まれていないことを利用して、SIGHASH_NOINPUTを使うのと同様にインプットを変更できるようなスクリプトを構築します。

手順

  1. オーナーはトランザクションのアウトプットを決め、suffixだけを構築して署名ownerSigを作成します
    • インプットはまだないのでprefixは存在しません
    • アウトプット、redeem script、suffix、ownerSigはUTXOをアンロックするために共有します
  2. コインを受け取るために、スクリプト宛て(P2SHアドレス)に支払ってもらいます
  3. アンロックする人はトランザクションを構築し、秘密鍵(covenantPrivKey)を用意してトランザクションに署名します
    • もし使用するインプットが変更された場合は、署名をし直します

スクリプト

scriptSigとredeem scriptは以下のようになります。

  • scriptSigに必要なパラメータ
    • <ownerSig> - オーナーが作成したsuffixへの署名
    • <prefix> - 上記のシリアライズフォーマットの1-7
    • <suffix> - 上記のシリアライズフォーマットの8-10
    • <covenantPubKey> - トランザクションの署名検証に使う任意の公開鍵
    • <covenantSig> - <covenantPubKey>で検証する、トランザクション全体の署名
  • redeem scriptで検証する内容
    1. OP_CHECKSIGで<covenantPubKey><covenantSig>を使いトランザクションを検証します
    2. <prefix><suffix>を結合してpreimageにします
    3. OP_CDSで<covenantPubKey><covenantSig>を使いpreimageを検証します
    4. OP_CDSでownerPubKey<ownerSig>を使い<suffix>を検証します
      • ownerPubKeyは予めredeem scriptの中で指定します

こちらはBitcoin Scriptで実装する例が元の記事にあり、Spednで実装する例もコメントされています。

おわりに

OP_CHECKDATASIGとその使い道としてCovenantsについて紹介しました。
今回紹介したもの以外にもカード型オフラインウォレット*2やSNSでのチップ*3などいろいろな使い道が提案されているので、興味があれば調べてみてください。 また、何か面白い使い道を思いついたら自分で実際に作ってみるといい経験になると思います。

一緒にCoincheckを盛り上げてくれるメンバーを募集しています!

様々なポジションを募集しておりますので、是非ご興味ある方はご応募ください。まずは話を聞いてみたいという方も大歓迎です。

参考