Develop with pleasure!

福岡でCloudとかBlockchainとか。

Bitcoin Cashのトランザクションを作成してBCHを入手してみた

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を入手できる。