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) 2019年3月22日
アリスが、ボブが支払い条件を満たすトランザクションを公開し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とするか課題は残る。