Gincoでバックエンドを担当している髙妻(@Tomokazu106)です。
本記事では実際にGincoウォレットで導入されているBitcoinCashとBitcoinSVのスプリット方法について解説します。
- BCHとBSVの特徴と現在の問題点(トランザクションリプレイ)
- 3つのスプリット方法(DustTransaction、nLockTime、OP_CODE)
- OP_CHECKDATASIGVERIFYについて
- P2PKHとP2SHの基礎
- 4-1 P2PKH
- 4-2 P2SH
- P2PKHとP2SHを使ったスプリット方法
- 5-1. OP_CHECKDATASIGを使ったRedeem Scriptの構築
- 5-2. P2PKH-nested-in-P2SH
1. BCHとBSVの特徴と現在の問題点(トランザクションリプレイ)
BCHとBSVは2018年11月16日にBlock Height 556767でハードフォークし、ブロックチェーンが2つに別れました。 原因はBitcoinABCとnChainそれぞれが開発を行なっていたBCHノードに異なる仕様の実装が追加されたためです。
それぞれのノードに追加された変更は下記です。
BitcoinABC
- 新規OP_CODEの追加:OP_CHECKDATASIG、OP_CHECKDATASIGVERIFY
- Canonical Transaction Ordering(CTOR)の導入
nChain
- オリジナルのOP_CODEの復元:OP_MUL、OP_LSHIFT、OP_RSHIFT、OP_INVERT
- 1スクリプトあたり201OP_CODEの制限を取り除く
- 最大ブロックサイズを128MBまで拡大
この仕様の違いにより2つのノードが分裂に至ったわけですが、それぞれの思想をプロトコルに落とし込んで開発しているので仕方ありません。 しかし、利用者側からするとブロックが分裂し、安全に送金できないという問題があります。Block Height 556767でのハードフォーク時点でリプレイプロテクションが実装されていなかったため、2019年3月現在もトランザクションリプレイが発生してしまいます。
リプレイプロテクションは2019年5月にBitcoinABCのノードに実装予定となっているので、それまではBCHとBSVがくっついたUTXOを送金する際はトランザクションリプレイが発生するので注意してください。 トランザクションリプレイの詳細については弊社技術ブログで解説した下記を参照してください。
2. 3つのスプリット方法(DustTransaction,nLockTim,OP_CODE)
トランザクションリプレイを防ぐためにBCHとBSVのUTXOをスプリット(分離)する必要があります。リプレイプロテクションが実装されるまで待つこともできますが、2019年5月に確実に実装されるとは限りません。 したがって、ウォレットを運営する事業者として利用者の資産保護の観点で独自にスプリットする必要があります。
BCHとBSVのUTXOをスプリットするには現状3つの方法があります。DustTransactionとnLockTimeを使ったスプリットについては以前解説しているので下記を参照してください。
Gincoで導入したOP_CODEを使ったスプリットにはBitcoinABCのノードに新たに追加されたOP_CHECKDATASIGを使います。 OP_CHECKDATASIGはBCHのノードでのみ有効なため、このOP_CODEを含んだトランザクションはBSVのノードで無効となりブロックに取り込まれません。 2つのノードの仕様の違いを利用してスプリットを行うことができます。
それぞれの方法で対応したときのメリット/デメリットをまとめるとこのようになります。
スプリット方法 | メリット | デメリット |
DustTransaction | 実装がいらない<br />容易に実行できる | スプリットするには分離済のUTXO(Gincoの場合BCH)を受け取らないといけない<br />ウォレット事業者としては分離済UTXOを全てのウォレットに対して送らないといけない |
nLockTime | 実装がいらない | BCHとBSVのブロック高に影響される<br />知識がないと送金タイミングがわからない |
OP_CODE | 任意のタイミングで自由にスプリットできる | 実装が大変 |
Gincoでは下記要件に適していることからOP_CODEを使用する方法を採択しました。
- 任意のタイミングで何回もスプリットできる
- 知識がなくとも簡単に実行できる
- 事業者側に負担がかからない
スプリットすべき対象が少ない場合はDustTransactionを使った方法がもっとも簡単なのでこの方法を使用することをおすすめします。
3. OP_CHECKDATASIGについて
通常の送金の署名検証で使われるOP_CHECKSIGでは署名(signature)とそれに対応する公開鍵(pubkey)をスタックに積めば、比較対象のmessageはノード側で計算して用意してくれますが、OP_CHECKDATASIGは任意のmessageを渡すことで外部データの検証を可能にしています。
OP_CHECKDATASIGの詳細な仕様はbitcoincashorg[1]でまとめられています。要点は下記です。
- BCHブロックチェーン以外の署名検証を行える
- オラクルの使用やクロスチェーンのアトミックスワップへの使用が可能になる
OP_CHECKDATASIGが追加されたことでアトミックスワップが現実味を帯びるのは非常に面白いOP_CODEだと思います。
スクリプトで記述すると下記のようになります。
<signature> <message> <public key> OP_CHECKDATASIG
4. P2PKHとP2SHの基礎
OP_CODEを使ったスプリットを理解するためにP2PKHとP2SHの理解は欠かせないので簡単に解説します。
BTC、BCH、BSVなどの通貨はUTXOのoutputに含まれるLocking Script(scriptPubKey)を解除するためにUnlocking Script(scriptSig)をinputに入れて解除することで使用できるわけですが、P2PKHとP2SHは使用するScriptの中身が異なります。
また、P2PKHとP2SHはスクリプトだけでなくアドレスの先頭に現れる文字も異なります。
スクリプトの種類 | testnet | livenet |
P2PKH | m始まり | 1始まり |
P2SH | 2始まり | 3始まり |
4-1. P2PKH Pay-to-Public-Key-Hash
P2PKHはBCHとBSVで使われている一般的な送金方法です。
※補足解説:スクリプト中に現れる<>
は中の値をスタックにプッシュするという意味です。
Locking Script
20-bytes-pubkey-hashは送金先の公開鍵をRIPEMD160(SHA256(pubkey))した値で20 Bytesのハッシュ値になります。
OP_DUP OP_HASH160 <20-bytes-pubkey-hash> OP_EQUALVERIFY OP_CHECKSIG
Unlocking Script
自身の秘密鍵で署名した結果と、秘密鍵に対応する公開鍵をセットします。
<signature> <pubkey>
Unlocking ScriptとLocking Scriptを同時に実行して検証が成功すれば送金することができます。
4-2. P2SH Pay-to-Script-Hash
P2SH[2]は主にマルチシグを利用するために使われていますが、スクリプト(Redeem Script)はP2SH以外のスクリプトであればなんでも入れることができます。 例えばRedeem Scriptの中にP2PKHの処理を入れることもできます。これは次章で解説します。
P2SHの特徴は大きく2つです。
- P2PKHと大きく異なるのはLocking Script、Unlocking Scriptの他にRedeem Scriptを定義
- P2SHのスクリプトの検証は2回に分けて行われる
MultiSigを例に各スクリプトを記述するとこのようになります。
Redeem Script
Redeem Scriptにはマルチシグに関するスクリプトを記述します。例は2of3のマルチシグです。
2 PubKey1 PubKey2 PubKey3 3 OP_CHECKMULTISIG
Locking Script
Redeem ScriptをRIPEMD160(SHA256(Redeem Script))した20 Bytesのハッシュ値が入ります。
OP_HASH160 <20-bytes-redeem-script-hash> OP_EQUAL
Unlocking Script
Unlocking Scriptの最後にRedeem Scriptが入ります。
<signature1> <signature2> <redeem script>
P2SHは2回に分けてスクリプトの実行が行われるわけですが、下記順番で2回の検証が行われます。
- 1回目はシリアライズされたRedeem Scriptのハッシュ値がLocking Scriptで指定されているものと一致するか検証
- 2回目はデシリアライズしたRedeem ScriptをLocking Scriptとして実行
上記2回の検証を通ることでUTXOを使用することができます。
5. BCHとBSVのスプリット方法
5-1. スプリットの流れ
スプリットがどのように行われるか概要を説明します。
Block Height 556767でブロックチェーンが分岐した後に送金を行なっていない場合はBCHとBSVのブロックチェーン両方に同じUTXOが存在していることになります。 このUTXOをBCHとBSVにスプリットさせるために全額送金のトランザクションを2回発行します。
1回目のトランザクション
1回目のトランザクションの目的はoutputにOP_CHECKDATASIGVERIFYを含んだトランザクションを作ることです。
所持しているUTXOを全てinputに使用し、2つのoutputを作ります。 1つはOP_CHECKDATASIGVERIFYを含んだ自分のP2SHアドレスへのoutput、もう1つは送金手数料分を引いたP2PKHのお釣りアドレスへのoutputです。
このトランザクションはBCHとBSVの両方で有効なトランザクションとなるためどちらの残高も同じになります。必要なのは少額の送金手数料のみです。
また、OP_CHECKDATASIGVERIFYを含んだP2SHアドレスに紐づく資産はBSV側では使うことができないためBCHで送金可能な最低額546satoshiを設定することで使用できなくなる額を最小限にしています。
2回目のトランザクション
2回目のトランザクションの目的はOP_CHECKDATASIGVERIFYを含んだUTXOを解除し、P2PKHアドレスに送金することです。
1回目で作成したP2SHとP2PKHのUTXOを2回目のトランザクションのinputにします。 outputは送金手数料を引いた全額を自分のP2PKHアドレスに対して送金します。
このトランザクションをブロックで取り込むことができるのはOP_CHECKDATASIGVERIFYが有効なBitcoinABCのノードだけとなります。 したがって、BCHの送金は完了しますが、BSVの送金は失敗し、同じだったUTXOを分離することができます。
最終的にBCHの有効な残高は2回目のトランザクションのoutput、BSVの有効な残高は1回目のトランザクションのお釣りアドレスへの送金分になります。
5-2. OP_CHECKDATASIGを使ったRedeem Scriptの構築
実際にスプリットのためのRedeem Scriptを構築すると下記のようになります。
Redeem Script
<oracle signature> <oracle message> <oracle public key> OP_CHECKDATASIGVERIFY
OP_CHECKDATASIGVERIFYを使いたいだけなのでRedeem Script内で署名検証が完結するように実装します。検証に使うオラクルデータは署名検証が通る適当なデータを用意してください。
5-3. P2PKH-nested-in-P2SH
上記Redeem ScriptでロックしたUTXOだと誰でも解除して使用することができてしまうので、Redeem ScriptにP2PKHを組み込みます。こうすることで公開鍵に対する秘密鍵を持った人しかこのUTXOを使うことができないようになります。
BitcoinのSegWit対応でも同様の方法は使われていて、Bech32に完全に移行する前の段階としてP2WPKH-nested-in-P2SHがBIP-49[3]で定義されています。
Redeem Script
OP_CHECKDATASIGVERIFY以降がP2PKHのスクリプトになります。このようにP2SHは自由にスクリプトを組み込むことができます。
<oracle signature> <oracle message> <oracle public key> OP_CHECKDATASIGVERIFY OP_DUP OP_HASH160 <20-bytes-pubkey-hash> OP_EQUALVERIFY OP_CHECKSIG
※Redeem Scriptは再帰処理ができないようになっているためP2SHを組み込むことはできません。またOP_RETURNも含むことができません。
Locking Script
Locking Scriptは通常のP2SHと同じで上記Redeem Scriptのハッシュ値をセットします。
OP_HASH160 <20-bytes-redeem-script-hash> OP_EQUAL
Unlocking Script
Unlocking Scriptは通常のP2PKHで使用する署名と公開鍵の最後にRedeem Scriptをセットするだけです。
注意点として、Redeem Scriptのサイズが75 Bytesを超えるのでOP_PUSHDATA1を使うようにしています。[4]
<signature> <public key> OP_PUSHDATA1 redeem script
まとめ
2019年3月時点ではBCHとBSVにリプレイプロテクションが実装されていないためOP_CODEを使ったリプレイプロテクションの解説と実装方法を紹介しました。 OP_CODEの仕様を調べて実際に実装して送金までできるようになることでスクリプトへの理解が増すので皆さんもぜひ試してみてください。
Tip us!
エンジニアチームがブログを書くモチベーションが上がります!