LoginSignup
5

More than 5 years have passed since last update.

Bitcoin::Builderを使ったトランザクション作成

Last updated at Posted at 2016-07-02

bitcoin-cliのsendtoaddressなどの標準送金コマンドで物足りない方向けです。
特定のUTXOから送金したいときに便利です。

ゆくゆくは、P2SH(スクリプトハッシュ)宛の送金を狙いたいのですが、まずは学習のためにP2PKH(公開鍵ハッシュ)宛のUTXOを使ってみます。

環境

  • Bitcoind: 0.12.1
  • Openassets-ruby: 0.4.8
  • Ruby: 2.2.2

Openassets-rubyは、bitcoinのapiコールのために使っています。直接bitcoin-apiを使う方は不要です。

参考 → http://qiita.com/osada/items/50fc76959e6730603417

作りたいトランザクション

Aさん(1BTC保有)、Bさん(1BTC保有)の2名から、Cさんに送金(1.999BTC)するトランザクション。
なお、Aさん、Bさんの秘密鍵は同一のマシンにあるものとする。

初期設定

必要なモジュールをロードして、bitcoindに接続するAPIオブジェクトを作成。
※以降、irbで実行することを想定しています。

require 'bitcoin'
require 'openassets'
require 'pp'

# testnetのおまじない
Bitcoin.network = :testnet3

### use zero confirmation
oa_api = OpenAssets::Api.new({:network => 'testnet',
:provider => 'bitcoind', :cache => 'testnet.db',
:dust_limit => 600, :default_fees => 10000,
:min_confirmation => 0, :max_confirmation => 9999999,
:rpc => {:user => 'osada', :password => 'password', :schema => 'http',
:port => 18332, :host => 'localhost'}})

送金に使うUTXO指定

送金に使用するUTXOを指定。

# 送信者AさんのUTXO # 1 BTC
A_addr           = 'mt22rKhAonbqfvyqSW5qRFnZwi1WyYFczv'

# UTXOを指定するためのトランザクションIDと出力部(vout)の番号
A_prev_txid      = 'b1bdba74d2bcbfe8f9701949af1faa817ed48010751ade23e38b351be723eb56'
A_prev_out_index = 0


# 送信者BさんのUTXO # 1 BTC
B_addr           = 'mhARub16mC2cP8si8PprPM8Z8UTvYdGDLr'

B_prev_txid      = 'b971714785ec66f852b79f0a5d2784f73110d6ff5a331fcdd58109463ecdaaff'
B_prev_out_index = 0

受信者の指定

# Cさんのアドレス
C_addr           = 'mt22rKhAonbqfvyqSW5qRFnZwi1WyYFczv'

秘密鍵の入手(vin検証&署名用)

# Aさんの秘密鍵(bitcoin-apiから入手)
A_priv_key = oa_api.provider.dumpprivkey(A_addr)
# => "cSkArVdNo1X9roQrCmnhZ3ifhzs7zF3cbhUHPCvjLoQXncNuBNYZ" 

# Keyオブジェクトに変換
A_key_obj = Bitcoin::Key.from_base58(A_priv_key)
# => #<Bitcoin::Key:0x007ff8fda7f0a0 @key=#<OpenSSL::PKey::EC:0x007ff8fda7f050 @group=nil>, @pubkey_compressed=true> 

# Bさんの秘密鍵(bitcoin-apiから入手
B_priv_key = oa_api.provider.dumpprivkey(B_addr)
# => "cTvh2ijnwt79QSfZKrYmDvAhByamNKCppG6PeBM38fYrgj67Qmai" 

# 同様に、Keyオブジェクトに変換
B_key_obj = Bitcoin::Key.from_base58(B_priv_key)
# => #<Bitcoin::Key:0x007ff8fe9b1410 @key=#<OpenSSL::PKey::EC:0x007ff8fe9b13c0 @group=nil>, @pubkey_compressed=true> 

指定したUTXOを含むトランザクションを入手

# 上で指定したtxidから、hex形式で入手
A_prev_tx_bin = oa_api.provider.getrawtransaction(A_prev_txid)
# => "01000000019c30b660df70bc4e327152e97073197621084967e1b1441a3c1641bfb2f3c867010000006a47304402207e05c734e8396c820779f292f475859342fccc3cd2f76f34efa32d02229738a402201f667162b42eb200e78d8c770cff9ce03433e01c1ae5d428a9123efe2eaa2ee7012103175ad13ad982e37c4aad5f426942d94c74ae2d4d85cc3bea13fef44c2acd3a01feffffff0200e1f505000000001976a91489216031ab89482495974a49f40a38f835d3739388ac6c0f1a1e010000001976a914deb8a31e71cf0d8af4e4270396408edb58091bcf88ac4a000000" 

# hex形式からオブジェクトを作成
A_prev_tx = Bitcoin::Protocol::Tx.new(A_prev_tx_bin.htb)
# => #<Bitcoin::Protocol::Tx:0x007ff8fe9c98a8 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007ff8fe9c93d0 @value=100000000, @pk_script_length=25, @pk_script="v\xA9\x14\x89!`1\xAB\x89H$\x95\x97JI\xF4\n8\xF85\xD3s\x93\x88\xAC">, #<Bitcoin::Protocol::TxOut:0x007ff8fe9c9240 @value=4799991660, @pk_script_length=25, @pk_script="v\xA9\x14\xDE\xB8\xA3\x1Eq\xCF\r\x8A\xF4\xE4'\x03\x96@\x8E\xDBX\t\e\xCF\x88\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007ff8fe9c9678 @prev_out_hash="\x9C0\xB6`\xDFp\xBCN2qR\xE9ps\x19v!\bIg\xE1\xB1D\x1A<\x16A\xBF\xB2\xF3\xC8g", @prev_out_index=1, @script_sig_length=106, @script_sig="G0D\x02 ~\x05\xC74\xE89l\x82\ay\xF2\x92\xF4u\x85\x93B\xFC\xCC<\xD2\xF7o4\xEF\xA3-\x02\"\x978\xA4\x02 \x1Ffqb\xB4.\xB2\x00\xE7\x8D\x8Cw\f\xFF\x9C\xE043\xE0\x1C\x1A\xE5\xD4(\xA9\x12>\xFE.\xAA.\xE7\x01!\x03\x17Z\xD1:\xD9\x82\xE3|J\xAD_BiB\xD9Lt\xAE-M\x85\xCC;\xEA\x13\xFE\xF4L*\xCD:\x01", @sequence="\xFE\xFF\xFF\xFF">], @lock_time=74, @ver=1, @enable_bitcoinconsensus=false, @payload="\x01\x00\x00\x00\x01\x9C0\xB6`\xDFp\xBCN2qR\xE9ps\x19v!\bIg\xE1\xB1D\x1A<\x16A\xBF\xB2\xF3\xC8g\x01\x00\x00\x00jG0D\x02 ~\x05\xC74\xE89l\x82\ay\xF2\x92\xF4u\x85\x93B\xFC\xCC<\xD2\xF7o4\xEF\xA3-\x02\"\x978\xA4\x02 \x1Ffqb\xB4.\xB2\x00\xE7\x8D\x8Cw\f\xFF\x9C\xE043\xE0\x1C\x1A\xE5\xD4(\xA9\x12>\xFE.\xAA.\xE7\x01!\x03\x17Z\xD1:\xD9\x82\xE3|J\xAD_BiB\xD9Lt\xAE-M\x85\xCC;\xEA\x13\xFE\xF4L*\xCD:\x01\xFE\xFF\xFF\xFF\x02\x00\xE1\xF5\x05\x00\x00\x00\x00\x19v\xA9\x14\x89!`1\xAB\x89H$\x95\x97JI\xF4\n8\xF85\xD3s\x93\x88\xACl\x0F\x1A\x1E\x01\x00\x00\x00\x19v\xA9\x14\xDE\xB8\xA3\x1Eq\xCF\r\x8A\xF4\xE4'\x03\x96@\x8E\xDBX\t\e\xCF\x88\xACJ\x00\x00\x00", @hash="b1bdba74d2bcbfe8f9701949af1faa817ed48010751ade23e38b351be723eb56"> 

# 同様にBさんのトランザクション
B_prev_tx_bin = oa_api.provider.getrawtransaction(B_prev_txid)
# => "01000000015eaff9fe4026c3c0b278b8bcebdb0d0d056c81c17dc37bbc628a1fd8f40881b6010000006b483045022100c5eda7ce4189f678a0498e15bc629e7088824dafdb0b8dfabf524816c4dacebb022008db321cf7652ce0b1f77e00296727075ca392dce54beb219dcd02b23964ce8a01210351c046666efaf58a6255994c66ffc2b8dcae8b0cd31f13166c39f5eceb41f783feffffff0200e1f505000000001976a914120e90f5177de616964c2cbb5d40311547d419e888ac1c2a2e12010000001976a914e310b8e8fef8506c6cfd34169f6904e6d0c9cd5688ac77000000" 

# オブジェクト化
B_prev_tx = Bitcoin::Protocol::Tx.new(B_prev_tx_bin.htb)
# => #<Bitcoin::Protocol::Tx:0x007ff8fb8ffe98 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007ff8fb8ff9c0 @value=100000000, @pk_script_length=25, @pk_script="v\xA9\x14\x12\x0E\x90\xF5\x17}\xE6\x16\x96L,\xBB]@1\x15G\xD4\x19\xE8\x88\xAC">, #<Bitcoin::Protocol::TxOut:0x007ff8fb8ff830 @value=4599982620, @pk_script_length=25, @pk_script="v\xA9\x14\xE3\x10\xB8\xE8\xFE\xF8Pll\xFD4\x16\x9Fi\x04\xE6\xD0\xC9\xCDV\x88\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007ff8fb8ffc90 @prev_out_hash="^\xAF\xF9\xFE@&\xC3\xC0\xB2x\xB8\xBC\xEB\xDB\r\r\x05l\x81\xC1}\xC3{\xBCb\x8A\x1F\xD8\xF4\b\x81\xB6", @prev_out_index=1, @script_sig_length=107, @script_sig="H0E\x02!\x00\xC5\xED\xA7\xCEA\x89\xF6x\xA0I\x8E\x15\xBCb\x9Ep\x88\x82M\xAF\xDB\v\x8D\xFA\xBFRH\x16\xC4\xDA\xCE\xBB\x02 \b\xDB2\x1C\xF7e,\xE0\xB1\xF7~\x00)g'\a\\\xA3\x92\xDC\xE5K\xEB!\x9D\xCD\x02\xB29d\xCE\x8A\x01!\x03Q\xC0Ffn\xFA\xF5\x8AbU\x99Lf\xFF\xC2\xB8\xDC\xAE\x8B\f\xD3\x1F\x13\x16l9\xF5\xEC\xEBA\xF7\x83", @sequence="\xFE\xFF\xFF\xFF">], @lock_time=119, @ver=1, @enable_bitcoinconsensus=false, @payload="\x01\x00\x00\x00\x01^\xAF\xF9\xFE@&\xC3\xC0\xB2x\xB8\xBC\xEB\xDB\r\r\x05l\x81\xC1}\xC3{\xBCb\x8A\x1F\xD8\xF4\b\x81\xB6\x01\x00\x00\x00kH0E\x02!\x00\xC5\xED\xA7\xCEA\x89\xF6x\xA0I\x8E\x15\xBCb\x9Ep\x88\x82M\xAF\xDB\v\x8D\xFA\xBFRH\x16\xC4\xDA\xCE\xBB\x02 \b\xDB2\x1C\xF7e,\xE0\xB1\xF7~\x00)g'\a\\\xA3\x92\xDC\xE5K\xEB!\x9D\xCD\x02\xB29d\xCE\x8A\x01!\x03Q\xC0Ffn\xFA\xF5\x8AbU\x99Lf\xFF\xC2\xB8\xDC\xAE\x8B\f\xD3\x1F\x13\x16l9\xF5\xEC\xEBA\xF7\x83\xFE\xFF\xFF\xFF\x02\x00\xE1\xF5\x05\x00\x00\x00\x00\x19v\xA9\x14\x12\x0E\x90\xF5\x17}\xE6\x16\x96L,\xBB]@1\x15G\xD4\x19\xE8\x88\xAC\x1C*.\x12\x01\x00\x00\x00\x19v\xA9\x14\xE3\x10\xB8\xE8\xFE\xF8Pll\xFD4\x16\x9Fi\x04\xE6\xD0\xC9\xCDV\x88\xACw\x00\x00\x00", @hash="b971714785ec66f852b79f0a5d2784f73110d6ff5a331fcdd58109463ecdaaff"> 

トランザクション構築

Builderクラスを使うと便利。

# Builderオブジェクト作成
tx_bldr = Bitcoin::Builder::TxBuilder.new
# => #<Bitcoin::Builder::TxBuilder:0x007ff8fe9a19e8 @tx=#<Bitcoin::Protocol::Tx:0x007ff8fe9a19c0 @scripts=[], @out=[], @in=[], @lock_time=0, @ver=1, @enable_bitcoinconsensus=false>, @ins=[], @outs=[]> 

入力部(vin)作成

# Aさんのvin。以下3つのブロックをinputメソッドに引数渡しして、登録する。
tx_bldr.input do |i|
  i.prev_out A_prev_tx
  i.prev_out_index A_prev_out_index
  i.signature_key A_key_obj
end
#  => [#<Bitcoin::Builder::TxInBuilder:0x007ff8fb8edbf8 @txin=#<Bitcoin::Protocol::TxIn:0x007ff8fb8edbd0 @prev_out_hash=nil, @prev_out_index=nil, @script_sig_length=0, @script_sig="", @sequence="\xFF\xFF\xFF\xFF">, @prev_out_hash="V\xEB#\xE7\e5\x8B\xE3#\xDE\x1Au\x10\x80\xD4~\x81\xAA\x1F\xAFI\x19p\xF9\xE8\xBF\xBC\xD2t\xBA\xBD\xB1", @prev_out_index=0, @prev_tx=#<Bitcoin::Protocol::Tx:0x007ff8fe9c98a8 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007ff8fe9c93d0 @value=100000000, @pk_script_length=25, @pk_script="v\xA9\x14\x89!`1\xAB\x89H$\x95\x97JI\xF4\n8\xF85\xD3s\x93\x88\xAC">, #<Bitcoin::Protocol::TxOut:0x007ff8fe9c9240 @value=4799991660, @pk_script_length=25, @pk_script="v\xA9\x14\xDE\xB8\xA3\x1Eq\xCF\r\x8A\xF4\xE4'\x03\x96@\x8E\xDBX\t\e\xCF\x88\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007ff8fe9c9678 @prev_out_hash="\x9C0\xB6`\xDFp\xBCN2qR\xE9ps\x19v!\bIg\xE1\xB1D\x1A<\x16A\xBF\xB2\xF3\xC8g", @prev_out_index=1, @script_sig_length=106, @script_sig="G0D\x02 ~\x05\xC74\xE89l\x82\ay\xF2\x92\xF4u\x85\x93B\xFC\xCC<\xD2\xF7o4\xEF\xA3-\x02\"\x978\xA4\x02 \x1Ffqb\xB4.\xB2\x00\xE7\x8D\x8Cw\f\xFF\x9C\xE043\xE0\x1C\x1A\xE5\xD4(\xA9\x12>\xFE.\xAA.\xE7\x01!\x03\x17Z\xD1:\xD9\x82\xE3|J\xAD_BiB\xD9Lt\xAE-M\x85\xCC;\xEA\x13\xFE\xF4L*\xCD:\x01", @sequence="\xFE\xFF\xFF\xFF">], @lock_time=74, @ver=1, @enable_bitcoinconsensus=false, @payload="\x01\x00\x00\x00\x01\x9C0\xB6`\xDFp\xBCN2qR\xE9ps\x19v!\bIg\xE1\xB1D\x1A<\x16A\xBF\xB2\xF3\xC8g\x01\x00\x00\x00jG0D\x02 ~\x05\xC74\xE89l\x82\ay\xF2\x92\xF4u\x85\x93B\xFC\xCC<\xD2\xF7o4\xEF\xA3-\x02\"\x978\xA4\x02 \x1Ffqb\xB4.\xB2\x00\xE7\x8D\x8Cw\f\xFF\x9C\xE043\xE0\x1C\x1A\xE5\xD4(\xA9\x12>\xFE.\xAA.\xE7\x01!\x03\x17Z\xD1:\xD9\x82\xE3|J\xAD_BiB\xD9Lt\xAE-M\x85\xCC;\xEA\x13\xFE\xF4L*\xCD:\x01\xFE\xFF\xFF\xFF\x02\x00\xE1\xF5\x05\x00\x00\x00\x00\x19v\xA9\x14\x89!`1\xAB\x89H$\x95\x97JI\xF4\n8\xF85\xD3s\x93\x88\xACl\x0F\x1A\x1E\x01\x00\x00\x00\x19v\xA9\x14\xDE\xB8\xA3\x1Eq\xCF\r\x8A\xF4\xE4'\x03\x96@\x8E\xDBX\t\e\xCF\x88\xACJ\x00\x00\x00", @hash="b1bdba74d2bcbfe8f9701949af1faa817ed48010751ade23e38b351be723eb56", @binary_hash="V\xEB#\xE7\e5\x8B\xE3#\xDE\x1Au\x10\x80\xD4~\x81\xAA\x1F\xAFI\x19p\xF9\xE8\xBF\xBC\xD2t\xBA\xBD\xB1">, @prev_out_script="v\xA9\x14\x89!`1\xAB\x89H$\x95\x97JI\xF4\n8\xF85\xD3s\x93\x88\xAC", @key=#<Bitcoin::Key:0x007ff8fda7f0a0 @key=#<OpenSSL::PKey::EC:0x007ff8fda7f050 @group=nil>, @pubkey_compressed=true>>] 

# Bさんのvin
tx_bldr.input do |i|
  i.prev_out B_prev_tx
  i.prev_out_index B_prev_out_index
  i.signature_key B_key_obj
end
# => [#<Bitcoin::Builder::TxInBuilder:0x007ff8fb8edbf8 @txin=#<Bitcoin::Protocol::TxIn:0x007ff8fb8edbd0 @prev_out_hash=nil, @prev_out_index=nil, @script_sig_length=0, @script_sig="", @sequence="\xFF\xFF\xFF\xFF">, @prev_out_hash="V\xEB#\xE7\e5\x8B\xE3#\xDE\x1Au\x10\x80\xD4~\x81\xAA\x1F\xAFI\x19p\xF9\xE8\xBF\xBC\xD2t\xBA\xBD\xB1", @prev_out_index=0, @prev_tx=#<Bitcoin::Protocol::Tx:0x007ff8fe9c98a8 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007ff8fe9c93d0 @value=100000000, @pk_script_length=25, @pk_script="v\xA9\x14\x89!`1\xAB\x89H$\x95\x97JI\xF4\n8\xF85\xD3s\x93\x88\xAC">, #<Bitcoin::Protocol::TxOut:0x007ff8fe9c9240 @value=4799991660, @pk_script_length=25, @pk_script="v\xA9\x14\xDE\xB8\xA3\x1Eq\xCF\r\x8A\xF4\xE4'\x03\x96@\x8E\xDBX\t\e\xCF\x88\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007ff8fe9c9678 @prev_out_hash="\x9C0\xB6`\xDFp\xBCN2qR\xE9ps\x19v!\bIg\xE1\xB1D\x1A<\x16A\xBF\xB2\xF3\xC8g", @prev_out_index=1, @script_sig_length=106, @script_sig="G0D\x02 ~\x05\xC74\xE89l\x82\ay\xF2\x92\xF4u\x85\x93B\xFC\xCC<\xD2\xF7o4\xEF\xA3-\x02\"\x978\xA4\x02 \x1Ffqb\xB4.\xB2\x00\xE7\x8D\x8Cw\f\xFF\x9C\xE043\xE0\x1C\x1A\xE5\xD4(\xA9\x12>\xFE.\xAA.\xE7\x01!\x03\x17Z\xD1:\xD9\x82\xE3|J\xAD_BiB\xD9Lt\xAE-M\x85\xCC;\xEA\x13\xFE\xF4L*\xCD:\x01", @sequence="\xFE\xFF\xFF\xFF">], @lock_time=74, @ver=1, @enable_bitcoinconsensus=false, @payload="\x01\x00\x00\x00\x01\x9C0\xB6`\xDFp\xBCN2qR\xE9ps\x19v!\bIg\xE1\xB1D\x1A<\x16A\xBF\xB2\xF3\xC8g\x01\x00\x00\x00jG0D\x02 ~\x05\xC74\xE89l\x82\ay\xF2\x92\xF4u\x85\x93B\xFC\xCC<\xD2\xF7o4\xEF\xA3-\x02\"\x978\xA4\x02 \x1Ffqb\xB4.\xB2\x00\xE7\x8D\x8Cw\f\xFF\x9C\xE043\xE0\x1C\x1A\xE5\xD4(\xA9\x12>\xFE.\xAA.\xE7\x01!\x03\x17Z\xD1:\xD9\x82\xE3|J\xAD_BiB\xD9Lt\xAE-M\x85\xCC;\xEA\x13\xFE\xF4L*\xCD:\x01\xFE\xFF\xFF\xFF\x02\x00\xE1\xF5\x05\x00\x00\x00\x00\x19v\xA9\x14\x89!`1\xAB\x89H$\x95\x97JI\xF4\n8\xF85\xD3s\x93\x88\xACl\x0F\x1A\x1E\x01\x00\x00\x00\x19v\xA9\x14\xDE\xB8\xA3\x1Eq\xCF\r\x8A\xF4\xE4'\x03\x96@\x8E\xDBX\t\e\xCF\x88\xACJ\x00\x00\x00", @hash="b1bdba74d2bcbfe8f9701949af1faa817ed48010751ade23e38b351be723eb56", @binary_hash="V\xEB#\xE7\e5\x8B\xE3#\xDE\x1Au\x10\x80\xD4~\x81\xAA\x1F\xAFI\x19p\xF9\xE8\xBF\xBC\xD2t\xBA\xBD\xB1">, @prev_out_script="v\xA9\x14\x89!`1\xAB\x89H$\x95\x97JI\xF4\n8\xF85\xD3s\x93\x88\xAC", @key=#<Bitcoin::Key:0x007ff8fda7f0a0 @key=#<OpenSSL::PKey::EC:0x007ff8fda7f050 @group=nil>, @pubkey_compressed=true>>, #<Bitcoin::Builder::TxInBuilder:0x007ff8fda55cf0 @txin=#<Bitcoin::Protocol::TxIn:0x007ff8fda55cc8 @prev_out_hash=nil, @prev_out_index=nil, @script_sig_length=0, @script_sig="", @sequence="\xFF\xFF\xFF\xFF">, @prev_out_hash="\xFF\xAA\xCD>F\t\x81\xD5\xCD\x1F3Z\xFF\xD6\x101\xF7\x84']\n\x9F\xB7R\xF8f\xEC\x85Gqq\xB9", @prev_out_index=0, @prev_tx=#<Bitcoin::Protocol::Tx:0x007ff8fb8ffe98 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007ff8fb8ff9c0 @value=100000000, @pk_script_length=25, @pk_script="v\xA9\x14\x12\x0E\x90\xF5\x17}\xE6\x16\x96L,\xBB]@1\x15G\xD4\x19\xE8\x88\xAC">, #<Bitcoin::Protocol::TxOut:0x007ff8fb8ff830 @value=4599982620, @pk_script_length=25, @pk_script="v\xA9\x14\xE3\x10\xB8\xE8\xFE\xF8Pll\xFD4\x16\x9Fi\x04\xE6\xD0\xC9\xCDV\x88\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007ff8fb8ffc90 @prev_out_hash="^\xAF\xF9\xFE@&\xC3\xC0\xB2x\xB8\xBC\xEB\xDB\r\r\x05l\x81\xC1}\xC3{\xBCb\x8A\x1F\xD8\xF4\b\x81\xB6", @prev_out_index=1, @script_sig_length=107, @script_sig="H0E\x02!\x00\xC5\xED\xA7\xCEA\x89\xF6x\xA0I\x8E\x15\xBCb\x9Ep\x88\x82M\xAF\xDB\v\x8D\xFA\xBFRH\x16\xC4\xDA\xCE\xBB\x02 \b\xDB2\x1C\xF7e,\xE0\xB1\xF7~\x00)g'\a\\\xA3\x92\xDC\xE5K\xEB!\x9D\xCD\x02\xB29d\xCE\x8A\x01!\x03Q\xC0Ffn\xFA\xF5\x8AbU\x99Lf\xFF\xC2\xB8\xDC\xAE\x8B\f\xD3\x1F\x13\x16l9\xF5\xEC\xEBA\xF7\x83", @sequence="\xFE\xFF\xFF\xFF">], @lock_time=119, @ver=1, @enable_bitcoinconsensus=false, @payload="\x01\x00\x00\x00\x01^\xAF\xF9\xFE@&\xC3\xC0\xB2x\xB8\xBC\xEB\xDB\r\r\x05l\x81\xC1}\xC3{\xBCb\x8A\x1F\xD8\xF4\b\x81\xB6\x01\x00\x00\x00kH0E\x02!\x00\xC5\xED\xA7\xCEA\x89\xF6x\xA0I\x8E\x15\xBCb\x9Ep\x88\x82M\xAF\xDB\v\x8D\xFA\xBFRH\x16\xC4\xDA\xCE\xBB\x02 \b\xDB2\x1C\xF7e,\xE0\xB1\xF7~\x00)g'\a\\\xA3\x92\xDC\xE5K\xEB!\x9D\xCD\x02\xB29d\xCE\x8A\x01!\x03Q\xC0Ffn\xFA\xF5\x8AbU\x99Lf\xFF\xC2\xB8\xDC\xAE\x8B\f\xD3\x1F\x13\x16l9\xF5\xEC\xEBA\xF7\x83\xFE\xFF\xFF\xFF\x02\x00\xE1\xF5\x05\x00\x00\x00\x00\x19v\xA9\x14\x12\x0E\x90\xF5\x17}\xE6\x16\x96L,\xBB]@1\x15G\xD4\x19\xE8\x88\xAC\x1C*.\x12\x01\x00\x00\x00\x19v\xA9\x14\xE3\x10\xB8\xE8\xFE\xF8Pll\xFD4\x16\x9Fi\x04\xE6\xD0\xC9\xCDV\x88\xACw\x00\x00\x00", @hash="b971714785ec66f852b79f0a5d2784f73110d6ff5a331fcdd58109463ecdaaff", @binary_hash="\xFF\xAA\xCD>F\t\x81\xD5\xCD\x1F3Z\xFF\xD6\x101\xF7\x84']\n\x9F\xB7R\xF8f\xEC\x85Gqq\xB9">, @prev_out_script="v\xA9\x14\x12\x0E\x90\xF5\x17}\xE6\x16\x96L,\xBB]@1\x15G\xD4\x19\xE8\x88\xAC", @key=#<Bitcoin::Key:0x007ff8fe9b1410 @key=#<OpenSSL::PKey::EC:0x007ff8fe9b13c0 @group=nil>, @pubkey_compressed=true>>] 

出力部(vout)作成

tx_bldr.output do |o|
  o.value 199900000 # 1.999 BTC in satoshis
  o.script {|s| s.recipient C_addr }
end
# => [#<Bitcoin::Builder::TxOutBuilder:0x007ff8fda35b80 @txout=#<Bitcoin::Protocol::TxOut:0x007ff8fda35b58 @value=199900000, @pk_script_length=25, @pk_script="v\xA9\x14\x89!`1\xAB\x89H$\x95\x97JI\xF4\n8\xF85\xD3s\x93\x88\xAC", @redeem_script=nil>>] 

トランザクション生成

送信するトランザクションをBuilderから生成する

# bitcoin-apiを通して送信できるように、Txオブジェクトをシリアライズする(16進数表示)
serialized_tx = tx_bldr.tx.to_payload.bth
# => "010000000256eb23e71b358be323de1a751080d47e81aa1faf491970f9e8bfbcd274babdb1000000006a4730440220119b4da202ef11446671d0b518b9b5d269aadcff77e16c277da93d5a0d8370cd02200377b8306d44b4cd3503a5f65b0db125c8864412cdba8981d9b258e334529c0c012103ad69651a245138ca3c528e37c31d87bed1ef56e203017fbebbedb368e0a96e36ffffffffffaacd3e460981d5cd1f335affd61031f784275d0a9fb752f866ec85477171b9000000006a47304402205c473c35077ed55eab402fee296cbcc450805719b094f3b4f8d3733c681fbb2e02206ec3aa738026d0463d9b1558bbfab404226480a80a08936741c0fe97d5eab0df01210274dbebf96a69dceb21967a98ae2a21df20b7075ddf5ab3d43fe5c4f06ce0c105ffffffff01603bea0b000000001976a91489216031ab89482495974a49f40a38f835d3739388ac00000000" 

# 念のため、中身を見ると、2つのvinと1つのvoutが存在することが確認できる
pp tx_bldr.tx.to_hash
# =>
{"hash"=>"2b68e73759e1857c3771b4a138f34202d621073d88ec8542ab1e143a527a7241",
 "ver"=>1,
 "vin_sz"=>2,
 "vout_sz"=>1,
 "lock_time"=>0,
 "size"=>338,
 "in"=>
  [{"prev_out"=>
     {"hash"=>
       "b1bdba74d2bcbfe8f9701949af1faa817ed48010751ade23e38b351be723eb56",
      "n"=>0},
    "scriptSig"=>
     "30440220119b4da202ef11446671d0b518b9b5d269aadcff77e16c277da93d5a0d8370cd02200377b8306d44b4cd3503a5f65b0db125c8864412cdba8981d9b258e334529c0c01 03ad69651a245138ca3c528e37c31d87bed1ef56e203017fbebbedb368e0a96e36"},
   {"prev_out"=>
     {"hash"=>
       "b971714785ec66f852b79f0a5d2784f73110d6ff5a331fcdd58109463ecdaaff",
      "n"=>0},
    "scriptSig"=>
     "304402205c473c35077ed55eab402fee296cbcc450805719b094f3b4f8d3733c681fbb2e02206ec3aa738026d0463d9b1558bbfab404226480a80a08936741c0fe97d5eab0df01 0274dbebf96a69dceb21967a98ae2a21df20b7075ddf5ab3d43fe5c4f06ce0c105"}],
 "out"=>
  [{"value"=>"1.99900000",
    "scriptPubKey"=>
     "OP_DUP OP_HASH160 89216031ab89482495974a49f40a38f835d37393 OP_EQUALVERIFY OP_CHECKSIG"}]}

送信実行

bitcoin-apiのsendrawtransactionを使って実行する

oa_api.provider.sendrawtransaction(serialized_tx)

既知のエラー

取扱い不可のUTXO

マイニングによって得られたUTXO(以下に例示)を使う時には、署名に失敗します。
(実務上ほとんど発生しないと思いますが、念のため。)

  "vout": [
    {
      "value": 50.00000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "026fab0d63cf13d6bc57ace182033115a3acf91b6aa70ca70cd7ff753edc9f97de OP_CHECKSIG",
        "hex": "21026fab0d63cf13d6bc57ace182033115a3acf91b6aa70ca70cd7ff753edc9f97deac",
        "reqSigs": 1,
        "type": "pubkey",
        "addresses": [
          "mpESE8kLTy3AjuydTCNPrfMT1m5WjucZbX"
        ]
      }
    }
  ]

scriptPubKeyを見ると、他のそれと違います。よって、Builderの署名機能を使った時に、署名エラーが発生。

tx = tx_bldr.tx
# =>RuntimeError: Signature error

P2SH

現在のバージョンでは、P2SHの署名はマルチシグのみサポート。マルチシグ以外のRedeemScriptを書いている場合は、P2SHと判定されずに、エラーになる。

エラー
RuntimeError: Script type must be hash160, pubkey or multisig
builder.rb:228:in `sig_hash_and_all_keys_exist?'

P2SHの署名は手動で作る必要あり。

あとがき

自分で署名を作りたいときや、P2SHを使うときの署名(検証スクリプトが、scriptSig + scriptPubKeyでない場合)をしたいときは、Builderでは対応できない。

次回(?)は、その場合の署名方法をご紹介します。

(2016-07-06更新) 次回作を書きました。
P2SHとP2PKH宛のUTXOを使った送金から学ぶBitcoinの署名の仕組み
http://qiita.com/osada/items/6d91ccff2b0cb4d34e29

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5