8/1にBitcoin Cashが分岐した際、BTCとの間のリプレイアタックに対する保護の仕組みを組み込む必要があり、この時SIGHASHを利用する仕組みが導入された。
spec/replay-protected-sighash.md at master · Bitcoin-UAHF/spec · GitHub
最近分岐したBitcoin Gold(BTG)も同じ仕組みを採用したようだ。
SIGHASH_FORKID
通常トランザクションに署名する際には、トランザクションのどのスコープまで署名するか決めるのにSIGHASH TYPEと呼ばれるものをセットするようになっている↓
techmedia-think.hatenablog.com
基本となるSIGHASH_ALL、SIGHASH_NONE、SIGHASH_SINGLEの3つタイプにSIGHASH_ANYONECANPAYというフラグを組み合わせることで計6通りの署名スコープをセットできるようになっている。
(ほとんどのトランザクションはSIGHASH_ALLを使っている。)
フォークを識別するため、SIGHASH_ANYONECANPAYのように追加されたフラグがSIGHASH_FORKIDで以下の値がフラグとして決められている。
SIGHASH_FORKID = 0x40
HF後、このフラグがセットされていないトランザクションはBCHやBTGのブロックチェーン上では無効なトランザクションと判断される。尚、BTCのチェーンでは、セットされているSIGHASH_TYPEからSIGHASH_ANYONECANPAYを除去した結果、その値がSIGHASH_ALL(0x01) 〜 SIGHASH_SINGLE(0x03)の間でなければ署名のエンコードチェックでエラーになる。つまり、 SIGHASH_FORKID(0x40)フラグがセットされていればエラーになる。
署名スコープがSIGHASH_ALLでBCHやBTGのトランザクションの署名を作る際、そのSIGHASH TYPEは
SIGHASH_ALL(0x01) | SIGHASH_FORKID(0x40) = 0x41
になる。
実際にBitcoin Cashのトランザクション↓
https://www.blocktrail.com/BCC/tx/50c22f9cb1fbeec34fb9a77fdd54bcfa65c6ae65deebc6e06655867d0d659b3d
のインプットのscriptSigを見てみると↓
304502210085b726543e566fe2921f801f548e18bfb67662de920f0264bee6086e610af553022011103703f8cbc19c33c213e546da169434f3334c20f456fdf36fd2ea33fe4e1b41 033489bdf777f135fa9ab0f31bd795a60a56d7daddc0ae18c6c1ad4d8589d2c72e
1つめの要素が署名で2つめの要素が公開鍵。署名の最後にはSIGHASH TYPEをセットする決まりなので、上記から41 = SIGHASH_ALL | SIGHASH_FORKIDになっていることが分かる。
41 = [ALL|FORKID]が適用されていることが分かる。
fork id
BCHもBTGもどちらもSIGHASH_FORKIDフラグが付与されているのは分かったが、これだけだとBTC⇔BCH,BTGのリプレイ保護にはなるけどBCH⇔BTG間のリプレイ保護にはならない。そのため共通のSIGHASH_FORKIDフラグとは別に各チェーンを識別するためのfork idが存在する。各チェーンのfork idの値は以下の通り。
| チェーン | fork id |
|---|---|
| BCH | 0 |
| BTG | 79(金の原子番号) |
このfork idは、署名対象のトランザクションのダイジェストデータであるSIGHASHを生成する際に加味される。BCHもBTGもトランザクションのダイジェストデータを生成する仕様は、BTCのsegwitで導入されたBIP-143のルールに従う↓(BCHはsegwit導入はしていないけどこの仕様だけは導入している)
techmedia-think.hatenablog.com
オリジナルのBIP-143と唯一違うのは、このSIGHASHを生成する際に使用するSIGHASH TYPEの最上位bitにfork idを含める点だ。BCHのfork idは0なので実質何もする必要はないが、BTGの場合は79なので↓のようにSIGHASH TYPEにfork idのビット和を適用する必要がある。
hash_type = hash_type | (79 << 8)
これにより、BTGのトランザクションをBCHのチェーンで署名検証すると、署名対象データであるSIGHASHを生成する際に使用する値(fork id)が違っているので署名検証に失敗し、リプレイ攻撃から保護できるという仕組みだ。
以上のSIGHASH_FORKIDとfork idがリプレイプロテクションの仕組みなので、この仕組みを採用しているBTC派生チェーンで有効なトランザクションを作成する際は、
- トランザクション署名時のSIGHASH TYPEに
SIGHASH_FORKIDフラグを適用 - SIGHASH生成時に使用するSIGHASH TYPEの最上位bitに
fork_idを適用(BCHのみ省略可)
すれば、それぞれのチェーンで有効なトランザクションが生成できる。トランザクションについては他はBTCと変わらないみたいなので、各チェーンのコインを送金したい場合は↑のルールに則って署名すればいいだけ。
気になる点
- この仕組みはSIGHASHを使った仕組みなので、当然ながら
OP_CHECKSIGやOP_CHECKMULTISIGを使用せず署名検証が必要のないスクリプトの場合、リプレイ保護は提供されない(まぁほとんどは署名検証をするスクリプトになっているということだろう)。 SIGHASH_FORKIDは署名の最後のSIGHASH TYPEを確認すれば簡単に分かるが、fork idはSIGHASH生成時の1要素なだけなので署名データから明示的に確認する方法はなく、そのチェーンで有効なfork idなのかは署名検証するまで分からない。- 署名検証のコストも考慮すると、ネットワークの
service bitsなんかで互いを識別して接続しないようにするとかした方が、各ノードが無駄なコストを負担しなくて済むように思うけどどうなんだろう?