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

bitcoin-rubyを使ってbitcoinのp2pkhトランザクションを作成します。
**bitcoinのトランザクションの構造について簡単な理解をしている前提です。**
[サンプルコード](https://gist.github.com/kannapoix/6a517be2eaecf760614966f8fbbc01bd)

# 環境
bitcoind v0.16
[最新版はこちらで確認](https://bitcoincore.org/en/releases/)
[bitcoincore.org](https://bitcoincore.org)
[bitcoin-ruby](https://github.com/lian/bitcoin-ruby)

# 必要な情報
1. 消費するutxoが含まれる、前トランザクションのid
2. 消費するutxoが含まれる、前トランザクションのoutputのindex
3. 消費するutxoに対応するpublic key script(locking script)
4. 署名する秘密鍵

```ruby
# 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`モジュール](https://github.com/lian/bitcoin-ruby/blob/master/lib/bitcoin/builder.rb)を利用します。
今回はトランザクションの作成なので、`Builder::build_tx`メソッドを利用します。

```ruby: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`を使っています。

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

```ruby:bitcoin.rb
module  BinaryExtensions
...
  # hex-to-bin
  def htb; [self].pack("H*"); end
...
end

class ::String
  include Bitcoin::BinaryExtensions
end
```

```ruby
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のバイナリに変換されます。

```ruby

# tx = prev_tx
@prev_out_hash = tx.htb.reverse
```

`Bitcoin::Protocol::Tx`オブジェクトの`@in`を見てみます。

```ruby: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にも必要な要素を与えます。

```ruby
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]

```ruby: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クラスのメソッド
```
```ruby: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に署名されます。

```ruby

def build_tx opts = {}
  c = TxBuilder.new
  yield c
  c.tx opts # ここ
end
```
この時点での`c`の状態は以下のようになっています。

```ruby
#<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完成

```ruby

# txはBuildTxのインスタンス
p tx.to_payload.unpack('H*')

# =>["0100000001e63746fe3e7ef7b1875036bd336dfad053e3be33c54c957b9d1df4ca37fd95b2010000006a473044022076d41dce70114e295899b64e76d2c23f5022969202a93bf88dbe1a32df5aa5e8022040d1ba492456123c678adb97023dea96ad8e2c951e8a80459ad84c8e398cd3f00121024f375377ff94aa8d4bfbd478d783730ad62db49b4a78e750ef3b8ace883d1236ffffffff01c0512677000000001976a9144873d013a8b12c1da4d853eb9268b43f9feecf2988ac00000000"]
```


[^1]: [dumpprivkeyで取得した秘密鍵から公開鍵を生成](https://techmedia-think.hatenablog.com/entry/2016/01/08/114213)
[^2]: [builder.rb](https://github.com/lian/bitcoin-ruby/blob/2d1f8f84b96623c160a96f435c8db155b028f201/lib/bitcoin/builder.rb#L365)
[^3]: [script.rb](https://github.com/lian/bitcoin-ruby/blob/master/lib/bitcoin/script.rb)