Bitcoin開発者の1人であるMatt Coralloが提案するコンセンサスルールのクリーンナップ↓
https://github.com/TheBlueMatt/bips/blob/cleanup-softfork/bip-XXXX.mediawiki
まだBIPにはなってないが、このクリーンナップの内容の1つがOP_CODESEPARATOR
の廃止だ。
OP_CODESEPARATORとは?
OP_CODESEPARATOR
はあまり見かけることがないopcodeの1つだが、これはOP_CHECKSIG
で署名検証する際にscriptPubKey
の一部だけをチェックするために使われる。
署名検証の手順
例えば通常OP_CHECKSIG
の署名検証は以下の手順で実行される。
- 公開鍵と署名データがスタックからポップされる。
- この時署名のフォーマットはDER形式で、署名データの最後にはSIGHASH_TYPEの値が付与されている。
- 署名からSIGHASH_TYPEを取り除き、保持する。
- 現在のトランザクションをコピーしたtxCopyを作成する。
- コピーしたtxCopyの全インプットのscriptSigを空にする。
- 署名検証対象のtxCopyのインプットに、
subScript
(後述)をセットする。 - SIGHASH_TYPEの値によってtxCopyの内容を調整する。
- txCopyをシリアライズし、double-SHA256する。
- 生成したハッシュ値とスタックからポップした公開鍵を使って署名の検証を行う。
subScriptとscriptCode
OP_CODESEPARATOR
が関連してくるのは、↑の手順6で出てくる該当インプットにセットするsubScript
の部分。subScript
は実際に実行されたコード = scriptCode
から作成される。
scriptCode
は、
- 非Segwitの場合
- P2SH以外はscriptPubkeyそのもの
- P2SHの場合はそのredeem script
- Segwitの場合
- P2WPKHの場合は、witness programをP2PKHスクリプトに変換した
0x1976a914{20-byte-pubkey-hash}88ac
- P2WSHのの場合は、witness script = Segwitにおけるredeem script
- P2WPKHの場合は、witness programをP2PKHスクリプトに変換した
となる。ほとんどの場合、scriptCode
がそのままsubScript
になるわけだが、scriptCode
のスクリプト内にOP_CODESEPARATOR
が含まれている場合だけ勝手が違ってくる。
OP_CODESEPARATORの作用
スクリプトにOP_CODESEPARATOR
が含まれる場合、最後にパースされたOP_CODESEPARATOR
からスクリプトの最後までがsubScript
になる。
例えば次のようなOP_CODESEPARATOR
を3つ含むscriptCode
があった場合、
[スクリプトパート1] OP_CODESEPARATOR [スクリプトパート2] OP_CODESEPARATOR OP_DUP OP_HASH160 <受信者の公開鍵ハッシュ> OP_EQUALVERIFY OP_CHECKSIG OP_CODESEPARATOR [スクリプトパート4]
スクリプトインタプリタがOP_CHECKSIG
を評価する際、最後にパースしたOP_CODESEPARATOR
は2つめのOP_CODESEPARATOR
になるため、それ以降のスクリプト↓がsubScript
になる。
OP_DUP OP_HASH160 <受信者の公開鍵ハッシュ> OP_EQUALVERIFY OP_CHECKSIG OP_CODESEPARATOR [スクリプトパート4]
そして、subScript
内のOP_CODESEPARATOR
は全て除去されるため、最終的なsubScript
は
OP_DUP OP_HASH160 <受信者の公開鍵ハッシュ> OP_EQUALVERIFY OP_CHECKSIG [スクリプトパート4]
となり、これが署名対象のSignature Hashを生成する際のトランザクションのインプットにセットされることになる。
※ 但し、BIP-143が適用されるSegwitスクリプトでは、subScript
内の全OP_CODESEPARATOR
の削除は行われない。
OP_CODESEPARATORの用途は?
OP_CODESEPARATOR
の動作は分かったとして、この複雑な機能はいったい何のためにあるのか?という疑問が湧き、2011年にbitcointalkでスレッドが立つも有用なユースケースは見つかっていない。
コメント頂きました。
一応このユースケースがあったはずなので、「有用なユースケースは見つかっていない。」というのは間違いかもしれません。https://t.co/y4Eqqk2nEK
— Joe Miyamoto (@joemphilips) March 22, 2019
アリスが、ボブが支払い条件を満たすトランザクションを公開しBOB_HASHのプリイメージを取得したい以下のようなスクリプトを考える。
OP_DEPTH 3 EQUAL OP_IF 2 <ALICE_KEY_PAYMENT> <BOB_KEY_PAYMENT> MULTICHECKSIGVERIFY HASH160 <BOB_HASH> EQUAL OP_ELSE <ALICE_KEY_REDEEM> CHECKSIG CLTV DROP OP_END
ただ、<ALICE_KEY_PAYMENT>と<ALICE_KEY_REDEEM>の鍵が同じであった場合、ボブはBOB_HASHのプリイメージを公開することなくアリスの署名を使ってコインを入手できるためよろしくない。
↑のスクリプトをOP_CODESEPARATOR
を使うことで以下のように変更することができる。
<ALICE_KEY> OP_DEPTH 3 EQUAL OP_IF OP_SWAP <BOB_KEY_PAYMENT> CHECKSIGVERIFY HASH160 <BOB_HASH> EQUAL OP_CODESEPARATOR OP_ELSE CLTV DROP OP_END CHECKSIG
↑のスクリプトでは<ALICE_KEY_PAYMENT>と<ALICE_KEY_REDEEM>が同じ<ALICE_KEY>として扱われるが、OP_IFの分岐とOP_ELSEの分岐でアリスが提供する署名は、OP_CODESEPARATOR
により署名対象のトランザクションダイジェストの値が変わるため、最初のスクリプトのようにボブが不正をできなくなる。
結果、アリスがOP_IFとOP_ELSE分岐で同じ公開鍵を使用することで公開鍵1つ分(=33バイト分)スクリプトデータを削減することができる。というのがOP_CODESEPARATOR
の用途として提案されている。
実際、上記の提案では、SegwitのスクリプトでOP_CODESEPARATOR
が利用できれば問題ないということで↓の非標準化は実現された。また、このユースケース自体はスクリプト全体を公開することなく、スクリプトの実行ブランチのみを公開する)MASTが利用可能になれば代替可能になる。
Bitcoin Core 0.16.1以降は非標準に
Bitcoin Core 0.16.1では以下のプルリクにより、非Segiwtなスクリプトにおいて、スクリプト内にOP_CODESEPARATOR
が含まれている場合、非標準トランザクションとみなし、リレーされなくなる。
そのため、現在標準トランザクションとしてOP_CODESEPARATOR
が利用できるのは、SegwitのP2WSHもしくは、PS2SH-P2WSHのみ。
OP_CODESEPARATORの弊害
↑のようにOP_CODESEPARATOR
を使うと署名対象のSignature Hashのデータを変更することができるため、大量の署名操作と共に悪用すると署名検証にえらい時間がかかるトランザクションを作成することが出来てしまう恐れがある(最悪30分とか)。
Bitcoin Core 0.16.1によりリレーされにくくなったとは言え、有用な用途もなく悪用される可能性のあるopcodeは廃止しようという流れだ。
本当に廃止される?
Bitcoin Core 0.16.1以降では非標準トランザクションとして判定されるものの、コンセンサスルールでは許可されてるため、廃止するにはソフトフォークが必要になる。OP_CODESEPARATOR
に限らず、Matt Coralloのクリーンナップの提案には無効なSIGHASH_TYPEを無効判定にするなどの提案もある。これらを有効化した場合、まだブロードキャストされていないが、それらの条件を使用した署名済みのトランザクションが保持されていて、かつその署名を作成した秘密鍵にアクセスすることができなくなってしまっている場合にコインを喪失する懸念がある。このようなユーザーがいる場合、ソフトフォークがアクティベートした後はそれらのトランザクションがブロックに取り込まれなくなるため、資金は永遠に失わえる。
そういった懸念から、OP_CODESEPARATOR
の数をトランザクションweightに加味するようなソフトフォークにした方がいいのでは?という提案もあるが、これをどう判断しBIPとするか課題は残る。