コールドウォレット開発グループの原です。
一昨年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でのトランザクションの署名方法について説明します。
トランザクションを署名するには、まず下記のように仕様で定められたフォーマットでトランザクションをシリアライズします。
- nVersion of the transaction (4-byte little endian)
- hashPrevouts (32-byte hash)
- hashSequence (32-byte hash)
- outpoint (32-byte hash + 4-byte little endian)
- scriptCode of the input (serialized as scripts inside CTxOuts)
- value of the output spent by this input (8-byte little endian)
- nSequence of the input (4-byte little endian)
- hashOutputs (32-byte hash)
- nLocktime of the transaction (4-byte little endian)
- 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で検証する内容
- OP_CHECKSIGで
<pubKey>と<sig>を使いトランザクションを検証します - OP_CDSで
<pubKey>と<sig>を使い<preimage>を検証します- 1.と同じ
<pubKey>と<sig>を使って検証するため、<preimage>が本当にトランザクションから作られたことが確認できます
- 1.と同じ
<preimage>に含まれる送金先の情報が期待通りか検証します
- OP_CHECKSIGで
このCovenantsの仕組みを応用したものとして、今回はLooping transactionとSIGHASH_NOINPUT emulationを紹介します。
Looping transaction
Looping transactionは、トランザクションの送金先に同じscriptPubKeyが使われていることを強制するトランザクションです。 Bitcoin Scriptはループを表現する機能を提供していませんが、Looping transactionで代用できるケースもあります。
Looping transactionを利用したものとして、Last WillやMecenasというウォレットのプラグインがあります。
スクリプト
scriptSigとredeem scriptは以下のようになります。
- scriptSigに必要なパラメータ
<pubKey>- 署名を検証するための公開鍵<sig>- トランザクションの署名<preimage>- シリアライズされたトランザクション
- redeem scriptで検証する内容
- OP_CHECKSIGで
<pubKey>と<sig>を使いトランザクションを検証します - OP_CDSで
<pubKey>と<sig>を使い<preimage>を検証します- 1.と同じ
<pubKey>と<sig>を使って検証するため、<preimage>がシリアライズされたトランザクションであることが確認できます
- 1.と同じ
<preimage>中の6. value of the output spent by this inputからトランザクションの送金額を計算する<preimage>中の5. scriptCode of the inputから自身のredeem scriptを取り出し、scriptPubKeyを生成する- 送金額とscriptPubKeyを使って期待するアウトプットを組み立てる
<preimage>中の8. hashOutputsが期待するアウトプットから作られたことを検証する
- OP_CHECKSIGで
このスクリプトはBitcoin Scriptで実装すると複雑になるため、SpednなどのBitcoin Scriptにコンパイルされる高級言語で実装されることが多いです。 Spednについてはまた機会があれば紹介したいと思いますが、参考にSpednで実装された例を紹介します。
- Last Will
https://github.com/KarolTrzeszczkowski/Electron-Cash-Last-Will-Plugin/blob/master/LastWill.spedn - Mecenas
https://github.com/KarolTrzeszczkowski/Mecenas-recurring-payment-EC-plugin/blob/master/mecenas_v3.spedn
SIGHASH_NOINPUT emulation
SIGHASH_NOINPUTは、Eltooなどのオフチェーンプロトコルに必要なSIGHASHです。
このSIGHASHを使うと、preimageをhashPrevoutsなどのインプットに関する情報を含めず構築できるようになり、preimageや署名を変更せずにトランザクションのインプットだけを変更できるようになります。
SIGHASH_NOINPUTはまだ実装されていないため使えませんが、OP_CDSを使って再現する方法が提案されています。
仕組み
まず、シリアライズフォーマット中の1-7をprefix、8-10をsuffixとして、preimageを分割します。
そして、トランザクションの所有者になる人(オーナー)はsuffixに署名をします。
suffixにインプットの情報が含まれていないことを利用して、SIGHASH_NOINPUTを使うのと同様にインプットを変更できるようなスクリプトを構築します。
手順
- オーナーはトランザクションのアウトプットを決め、suffixだけを構築して署名
ownerSigを作成します- インプットはまだないのでprefixは存在しません
- アウトプット、redeem script、suffix、
ownerSigはUTXOをアンロックするために共有します
- コインを受け取るために、スクリプト宛て(P2SHアドレス)に支払ってもらいます
- アンロックする人はトランザクションを構築し、秘密鍵(
covenantPrivKey)を用意してトランザクションに署名します- もし使用するインプットが変更された場合は、署名をし直します
スクリプト
scriptSigとredeem scriptは以下のようになります。
- scriptSigに必要なパラメータ
<ownerSig>- オーナーが作成したsuffixへの署名<prefix>- 上記のシリアライズフォーマットの1-7<suffix>- 上記のシリアライズフォーマットの8-10<covenantPubKey>- トランザクションの署名検証に使う任意の公開鍵<covenantSig>-<covenantPubKey>で検証する、トランザクション全体の署名
- redeem scriptで検証する内容
- OP_CHECKSIGで
<covenantPubKey>と<covenantSig>を使いトランザクションを検証します <prefix>と<suffix>を結合してpreimageにします- OP_CDSで
<covenantPubKey>と<covenantSig>を使いpreimageを検証します - OP_CDSで
ownerPubKeyと<ownerSig>を使い<suffix>を検証しますownerPubKeyは予めredeem scriptの中で指定します
- OP_CHECKSIGで
こちらはBitcoin Scriptで実装する例が元の記事にあり、Spednで実装する例もコメントされています。
- Bitcoin Script
https://gist.github.com/markblundeberg/79db7714c38bbba114dc192324f8382b#basic-example----single-owner-script - Spedn
https://gist.github.com/markblundeberg/79db7714c38bbba114dc192324f8382b#gistcomment-2910138
おわりに
OP_CHECKDATASIGとその使い道としてCovenantsについて紹介しました。
今回紹介したもの以外にもカード型オフラインウォレット*2やSNSでのチップ*3などいろいろな使い道が提案されているので、興味があれば調べてみてください。
また、何か面白い使い道を思いついたら自分で実際に作ってみるといい経験になると思います。
一緒にCoincheckを盛り上げてくれるメンバーを募集しています!
様々なポジションを募集しておりますので、是非ご興味ある方はご応募ください。まずは話を聞いてみたいという方も大歓迎です。
参考
- Digest algorithm
https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/replay-protected-sighash.md#digest-algorithm - OP_CHECKDATASIG
https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/op_checkdatasig.md - Last Will
https://github.com/KarolTrzeszczkowski/Electron-Cash-Last-Will-Plugin - Mecenas
https://github.com/KarolTrzeszczkowski/Mecenas-recurring-payment-EC-plugin - Spedn
https://spedn.readthedocs.io/en/latest/index.html - SIGHASH_NOINPUT
https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki - Eltoo
https://blockstream.com/eltoo.pdf - BCH floating transactions: SIGHASH_NOINPUT emulation using CHECKDATASIG covenants
https://gist.github.com/markblundeberg/79db7714c38bbba114dc192324f8382b