8/1のBitcoin CashのハードフォークでBitcoinがBTCとBCHに分岐した。分岐前にBTCを保持していると、それと同額のBCHも持っていることになる。
取引所などに預けていて、取引所がBCHに対応していれば何もしなくても取引所が同額のBCHが付与してくれる。モバイルウォレットなどでBTCを保管していた場合は、それぞれのウォレットがBitcoin Cashに対応していればBCHを入手できる。ウォレットがBCHに対応していない場合にBCHを入手するには以下のような方法が考えられる。
- BTCとBCHのウォレットが共にHDウォレットで、BIP-39のようなワードリストからマスターシードを復元できる共通の仕様をサポートしていれば、BTCのウォレットのワードリストをBCHのウォレットにインポートすることでBCHを入手できる。
- BTCを保持しているアドレスの秘密鍵をBCHのウォレットにインポートする。
- BTCを保持しているアドレスの秘密鍵を使ってBCHのトランザクションを作成してネットワークにブロードキャストする。
なんとなくウォレットのマスターシードを移行するのは気が引けたのと、Bitcoin Cashのトランザクションを作ってみたかったので最後の方法でBCHを入手してみた。
秘密鍵の入手
まず8/1以前からBTCを保持しているアドレスの秘密鍵を導出する。
ウォレットはmyceliumを使っていて、myceliumはBIP-32、BIP-39、BIP-44をサポートしているウォレットなので、ワードリストからマスターシードを復元し、階層的に鍵を導出することで、別のプログラムからでもmyceliumアプリ内で使用している鍵を導出することができる。
BIP-44に対応しているので以下のパス階層で鍵導出をする。
m / purpose' / coin_type' / account' / change / address_index
- purpose'
BIP-44なので指定するのは44の強化鍵 = 231 + 44 = 0x8000002C - coin_type'
Bitcoinなので0の強化鍵 = 231 + 0 = 0x80000000 - account'
ウォレットで使ってるアカウントのインデックス(0から始まる)の強化鍵 = = 231 + 0 = 0x80000000 - change
0が外部からBitcoinを受け取るためのアドレス、1が自分がBitcoinを送る際のおつり用のアドレスを示す定数。これは強化鍵ではないのでそのままの値を使用する(0)。 - address_index
0から始まるアドレスのインデックス。これも強化鍵ではないのでそのままの値を使用する。
以下、開発中のbitcoinrb(v0.1.3)を使ってmyceliumで使用している鍵を導出するコード↓
require 'bitcoin' mnemonic = Bitcoin::Mnemonic.new('english') # ワードリストからシードを復元 seed = mnemonic.to_seed(%w(abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about)) # シードからマスター拡張秘密鍵を生成 master = Bitcoin::ExtKey.generate_master(seed) # マスター拡張秘密鍵から1階層目の拡張秘密鍵を導出 level1 = master.derive(0x8000002C) # 1階層目の拡張秘密鍵からcoin typeを指定して2階層目の拡張秘密鍵を導出 level2 = level1.derive(0x80000000) # 2階層目の拡張秘密鍵からaccountを指定して3階層目の拡張秘密鍵を導出 level3 = level2.derive(2**31 + 0) # 3階層目の拡張秘密鍵からchangeを指定して4階層目の拡張秘密鍵を導出(おつり用の場合は1を指定) level4 = level3.derive(0) # 4階層目の拡張秘密鍵から決済に使用している秘密鍵を導出(とりあえず10個ほど導出) 10.times do |index| key = level4.derive(index) puts key.addr # アドレス puts key.priv # 秘密鍵 end
myceliumで使用されているアドレスが実際に導出できていたら成功。
このまま秘密鍵をBitcoin Cashのノードにインポートするのも可。
Bitcoin CashのSignature Hashの生成方法
Bitcoin Cashではリプレイ保護のため、Signature Hashの生成方法がBitcoinとは変わっている。
bitcoin-abc/replay-protected-sighash.md at master · Bitcoin-ABC/bitcoin-abc · GitHub
Bitcoinではall(1)
、none(2)
、single(3)
というSIGHASHタイプと、anyonecanpay(0x80)
フラグを組み合わせた合計6個のSIGHASHタイプが定義されているが、Bitcoin Cashにはforkid(0x40)
というフラグが新たに追加された。
Bitcoin Cashのトランザクションに署名する際はこのforkid(0x40)
を組み合わせる。このとき署名対象のメッセージダイジェストの生成方法はBIP-143の仕様に従う。
techmedia-think.hatenablog.com
Bitcoin Cashのトランザクションの作成
↑のforkid(0x40)
を使ってBitcoin Cashを送付するトランザクションをbitcoinrbで作ってみる↓
(bitcoinrb自体は今のところBitcoin Cashに対応はしてないけど、Cash用のトランザクション自体は以下のようにすれば作れる。)
require 'bitcoin' SIGHASH_FORKID = 0x40 # ロックされているUTXOの秘密鍵 key = Bitcoin::Key.new(priv_key: '秘密鍵') # UTXOを持つトランザクション prev_tx = Bitcoin::Tx.parse_from_payload('UTXOのトランザクションのペイロード'.htb) # 署名対象のTxを作る tx = Bitcoin::Tx.new out_pint = Bitcoin::OutPoint.new(prev_tx.txid, 0) tx_in = Bitcoin::TxIn.new(out_point: out_pint) tx.inputs << tx_in # 送付先 script_pubkey = Bitcoin::Script.to_p2pkh('送付先の公開鍵ハッシュ') tx.outputs << Bitcoin::TxOut.new(value: 送金するsatoshiの量, script_pubkey: script_pubkey) # SIGHASH ALLにFORK_IDフラグを付与 hash_type = Bitcoin::SIGHASH_TYPE[:all] | SIGHASH_FORKID # BIP-143準拠のメッセージダイジェストを生成 sighash = tx.sighash_for_input(0, prev_tx.outputs[0].script_pubkey, hash_type: hash_type, sig_version: :witness_v0, amount: prev_tx.outputs[0].value) # 署名生成(P2PKHの署名) sig = key.sign(sighash) + [hash_type].pack('C') tx_in.script_sig = Bitcoin::Script.parse_from_payload( Bitcoin::Script.pack_pushdata(sig) + Bitcoin::Script.pack_pushdata(key.pubkey.htb)) puts tx.to_payload.bth
出力されたペイロードをsendrawtransaction
とかブロードキャスト系のサービスでネットワークにブロードキャストし、トランザクションがブロックに入れられればBCHを入手できる。