LoginSignup
1
2

More than 3 years have passed since last update.

bitcoin-rubyでp2pkhトランザクションを作成する

Last updated at Posted at 2018-10-21

bitcoin-rubyを使ってbitcoinのp2pkhトランザクションを作成します。
bitcoinのトランザクションの構造について簡単な理解をしている前提です。
サンプルコード

環境

bitcoind v0.16
最新版はこちらで確認
bitcoincore.org
bitcoin-ruby

必要な情報

  1. 消費するutxoが含まれる、前トランザクションのid
  2. 消費するutxoが含まれる、前トランザクションのoutputのindex
  3. 消費するutxoに対応するpublic key script(locking script)
  4. 署名する秘密鍵
# 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メソッドを利用します。

builder.rb
# 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を使っています。

builder.rb
# 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クラスに対して定義されています。こちらを使ってもよいでしょう。

bitcoin.rb
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を見てみます。

in.rb
[#<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ですが、multisigwitness_hash160なども指定できます。
しかし、to_address_scriptはアドレスの種類を判別し、hash160、p2sh,witnessに応じたscriptを作成してくれるので、わざわざ指定する必要がないときもあるようです。3

builder.rb
# 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クラスのメソッド
script.rb
# 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メソッドで

builder.rb
@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"]
1
2
0

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
1
2