bitcoin-rubyを使ってbitcoinのp2pkhトランザクションを作成します。
bitcoinのトランザクションの構造について簡単な理解をしている前提です。
サンプルコード
環境
bitcoind v0.16
最新版はこちらで確認
bitcoincore.org
bitcoin-ruby
必要な情報
- 消費するutxoが含まれる、前トランザクションのid
- 消費するutxoが含まれる、前トランザクションのoutputのindex
- 消費するutxoに対応するpublic key script(locking script)
- 署名する秘密鍵
# 1
prev_tx = 'a6d52bb1b20b7fad41dad9b2bf4c25b76f8b18889f308a633ac82c913ca462aa'
# 2
out_index = 1
# 3
prev_script_sig = '76a91490a3e94240813b8b01859d9f2a12d028b53c44c788ac'
# 4
key = Bitcoin::Key.from_base58 'cU2ozeV3UcUTsmgmdywKJCjexjnQ53pASqov641zu5gBhu8uphsK'
from_base58
はWallet Import Format(WIF)からKeyオブジェクトをつくるメソッドです。
以下が`from_base58メソッドの説明です。
# Import private key from base58 fromat as described in
# https://en.bitcoin.it/wiki/Private_key#Base_58_Wallet_Import_format and
# https://en.bitcoin.it/wiki/Base58Check_encoding#Encoding_a_private_key.
# See also #to_base58
bitcoindにbitcoin-cli dumpprivkey <address>
した場合の戻り値はWIFなので、このメソッドを使うことは多いかと思います。1
Builder
bitcoin-rubyではトランザクションやブロックなどを作成する場合にはBuilder
モジュールを利用します。
今回はトランザクションの作成なので、Builder::build_tx
メソッドを利用します。
# build a Bitcoin::Protocol::Tx.
# see TxBuilder for details.
def build_tx opts = {}
c = TxBuilder.new
yield c
c.tx opts
end
TxBuilder
ブロックにTxBuilder
クラスのインスタンスが渡されています。
TxBuilder
内でinputとoutputをそれぞれ組み立てていきます。
input
TxBuilder.input
は以下です。
TxInBuilder
を使っています。
# add an input to the transaction (see TxInBuilder).
def input
c = TxInBuilder.new
yield c
@ins << c
end
TxInBuilderを使ったinputのつくりかた
準備した、前トランザクションid、output内でのindex、pubkey script、Keyオブジェクトを入れます。
prev_out
にTxオブジェクトではなく、トランザクションidを与えた場合は、prev_out_script
が必須になります。2
一つ注意しなければならないのは、prev_out_script
の引数です。
準備したprev_script_sig
は16進数文字列でしたが、prev_out_script
はバイナリを引数に取るようで、そのまま与えてもうまくいきません。
バイナリにする必要があります。
自分でpackしてもいいですが、bitcoin-ruby
にはhtb
というメソッドがStringクラスに対して定義されています。こちらを使ってもよいでしょう。
module BinaryExtensions
...
# hex-to-bin
def htb; [self].pack("H*"); end
...
end
class ::String
include Bitcoin::BinaryExtensions
end
TxInBuilder.new.input do |i|
i.prev_out prev_tx #tx hash
i.prev_out_index out_index
i.prev_out_script prev_script_sig.htb
i.signature_key key
end
prev_outはlitte endienのバイナリに変換されます。
# tx = prev_tx
@prev_out_hash = tx.htb.reverse
Bitcoin::Protocol::Tx
オブジェクトの@in
を見てみます。
[#<Bitcoin::Protocol::TxIn:0x00005583a37e0f00
@prev_out_hash=
"\xAE\"\xAB\x03e\xEB\xFE\x9By\xD9C\xD9\x02t7C\x87\x87\x81\xF7_\x82(\xB2r\x0F\xBB\xAE\xC9\xF2\xED\x02",
@prev_out_index=1,
@script_sig=
"H0E\x02!\x00\x83:\x02\xAB\x1F\xB0\x959\x893\xD4v(\xE5\x81\xFB\xD8\xD7(`N<\x7F\xCF\xAE\xAFS\b\x19\xE6-#\x02 Z\xB5\xDA\xA34\xE0p\x8C\x82x\xF4\x1A\xD0&@q@\xA2\xB4\x89j\xCF5\xBD\x81\xA2\xDBI!V\xF1\xE7\x01!\x02\xCF@\xE8R\xAA\xE8Z,\xEF\x0E\x9C\xBC\xF9x \x7F\xB9\xFFOF\xE0\xD73\xB2=\xEC(\xD1\xDF\xAB\xC0Q",
@script_sig_length=107,
@script_witness=
#<Bitcoin::Protocol::ScriptWitness:0x00005583a352e140 @stack=[]>,
@sequence="\xFF\xFF\xFF\xFF">]
output
同様にoutputにも必要な要素を与えます。
TxOutBuilder.new.output do |o|
o.value 1999000000 # satoshi
o.to 'n4Qtooa32CuQHj9qBzxShavn9rmukWUYJX' # address
end
toメソッドに渡されたアドレスは、Bitcoin::ScriptモジュールでBitcoinスクリプトに変換されます。
スクリプトの種類はtype
にしていできます。デフォルトはaddress
ですが、multisig
やwitness_hash160
なども指定できます。
しかし、to_address_script
はアドレスの種類を判別し、hash160、p2sh,witnessに応じたscriptを作成してくれるので、わざわざ指定する必要がないときもあるようです。3
# Set recipient address and script type (defaults to :address).
def to recipient, type = :address
@txout.pk_script, @txout.redeem_script = *Bitcoin::Script.send("to_#{type}_script", *recipient)
end
# sendはObjectクラスのメソッド
# generate hash160 or p2sh output script, depending on the type of the given +address+.
# see #to_hash160_script and #to_p2sh_script.
def self.to_address_script(address)
hash160 = Bitcoin.hash160_from_address(address)
case Bitcoin.address_type(address)
when :hash160; to_hash160_script(hash160)
when :p2sh; to_p2sh_script(hash160)
when :witness_v0_keyhash, :witness_v0_scripthash
witness_version, witness_program_hex = Bitcoin.decode_segwit_address(address)
to_witness_script(witness_version, witness_program_hex)
end
end
署名
署名はTxBuilderのメソッドtxで行われています。
最終的にはBitcoin::Util::sign_data
が呼び出されて対象のsig_hashに署名されます。
def build_tx opts = {}
c = TxBuilder.new
yield c
c.tx opts # ここ
end
この時点でのc
の状態は以下のようになっています。
# <Bitcoin::Builder::TxBuilder:0x0000562f9f92fcc8
@ins=
[#<Bitcoin::Builder::TxInBuilder:0x0000562f9f3f4510
@key=
#<Bitcoin::Key:0x0000562f9f738550
@key=#<OpenSSL::PKey::EC:0x0000562f9f738438>,
@pubkey_compressed=true>,
@prev_out_forkid=nil,
@prev_out_hash=
"\xE67F\xFE>~\xF7\xB1\x87P6\xBD3m\xFA\xD0S\xE3\xBE3\xC5L\x95{\x9D\x1D\xF4\xCA7\xFD\x95\xB2",
@prev_out_index=1,
@prev_out_script=
"v\xA9\x14\xF5\xF7\xD0\xE6iA\xB1\xEB\b\x99\xBDd\xE1\xD9Z\xA4@\x99\x06\xFA\x88\xAC",
@txin=
#<Bitcoin::Protocol::TxIn:0x0000562f9ef99cb8
@prev_out_hash=nil,
@prev_out_index=nil,
@script_sig="",
@script_sig_length=0,
@script_witness=
#<Bitcoin::Protocol::ScriptWitness:0x0000562f9f6afa70 @stack=[]>,
@sequence="\xFF\xFF\xFF\xFF">>],
@outs=
[#<Bitcoin::Builder::TxOutBuilder:0x0000562f9f6af778
@txout=
#<Bitcoin::Protocol::TxOut:0x0000562f9f8bf1a8
@pk_script=
"v\xA9\x14Hs\xD0\x13\xA8\xB1,\x1D\xA4\xD8S\xEB\x92h\xB4?\x9F\xEE\xCF)\x88\xAC",
@pk_script_length=25,
@redeem_script=nil,
@value=1999000000>>],
@tx=
#<Bitcoin::Protocol::Tx:0x0000562f9f3f52f8
@enable_bitcoinconsensus=false,
@in=[],
@lock_time=0,
@out=[],
@scripts=[],
@ver=1>>
TxBuilder::txメソッド
署名部分のコードは以下です。
sign_input
内のget_script_sig
メソッドで
@ins.each_with_index do |inc, i|
sign_input(i, inc)
end
# inc
# <Bitcoin::Builder::TxInBuilder:0x0000562f9f3f4510 @txin=#<Bitcoin::Protocol::TxIn:0x0000562f9ef99cb8 @prev_out_hash=nil, @prev_out_index=nil, @script_sig_length=0, @script_sig="", @sequence="\xFF\xFF\xFF\xFF", @script_witness=#<Bitcoin::Protocol::ScriptWitness:0x0000562f9f6afa70 @stack=[]>>, @prev_out_hash="\xE67F\xFE>~\xF7\xB1\x87P6\xBD3m\xFA\xD0S\xE3\xBE3\xC5L\x95{\x9D\x1D\xF4\xCA7\xFD\x95\xB2", @prev_out_index=1, @prev_out_forkid=nil, @prev_out_script="v\xA9\x14\xF5\xF7\xD0\xE6iA\xB1\xEB\b\x99\xBDd\xE1\xD9Z\xA4@\x99\x06\xFA\x88\xAC", @key=#<Bitcoin::Key:0x0000562f9f738550 @key=#<OpenSSL::PKey::EC:0x0000562f9f738438>, @pubkey_compressed=true>>
Tx完成
# txはBuildTxのインスタンス
p tx.to_payload.unpack('H*')
# =>["0100000001e63746fe3e7ef7b1875036bd336dfad053e3be33c54c957b9d1df4ca37fd95b2010000006a473044022076d41dce70114e295899b64e76d2c23f5022969202a93bf88dbe1a32df5aa5e8022040d1ba492456123c678adb97023dea96ad8e2c951e8a80459ad84c8e398cd3f00121024f375377ff94aa8d4bfbd478d783730ad62db49b4a78e750ef3b8ace883d1236ffffffff01c0512677000000001976a9144873d013a8b12c1da4d853eb9268b43f9feecf2988ac00000000"]