post Image
P2SHとP2PKH宛のUTXOを使った送金から学ぶBitcoinの署名の仕組み

動機

以前、Builderクラスを使ったトランザクションの作成についてご紹介しましたが、マルチシグ以外のP2SHのサポートがないことをみました。
そのため、任意のRedeemScriptを使ったUTXOから送金に利用するには、自力でトランザクションを作る必要になります。

この記事では、その手順をご紹介しながら、署名がどのように付けられるのか見ていきます。

環境

  • bitcoind 0.12.1
  • bitcoin-ruby 0.0.8
  • openassets-ruby 0.4.9 # APIやUtilを利用
  • 実行日: 2016-07-06

事前知識

2サイトをご紹介。

「Bitcoinを技術的に理解する 」
http://www.slideshare.net/kenjiurushima/20140602-bitcoin1-201406031222?next_slideshow=1
P.30以降、特に、P.34から説明されている署名生成方法は大変分かりやすい絵があります。
P2PKHの基本的な署名のやり方が理解できます。
まずは、イメージだけなんとなく理解できればOK。

「CHECKLOCKTIMEVERIFYで指定期間まで資金を凍結」
http://techmedia-think.hatenablog.com/entry/2016/05/31/184257
P2SHの実践的な使い方が理解できます。

分かりやすい記事を提供されているUrushima様と、業務で使える実践的なテクニックをご紹介されているAzuchi様に感謝いたします。

作成するトランザクション

番号 入力部(vin) 出力部(vout)
1 P2SH宛に送付された600satoshi ある宛先A(P2PKH)に600satoshi
2 P2PKH宛に送付された1BTC ある宛先A(P2PKH)に0.998BTC

※ 「入力部の合計」と「出力部の合計」との差は、手数料に相当。

初期設定

# モジュールロード
require 'bitcoin'
require 'openassets'
require 'pp'
include Bitcoin::Util

# おまじない
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を一意に特定するために、bitcoin-cliのlistunspentコマンドなどから、txidとvoutを取り出して、変数にセット。

# 0: P2SH宛に送られたUTXO(600satoshi)
# p2sh_addr           = '2NDideGsDwZSZSVo9X87zHB8ijYRzNQgdfR'
# P2SHでlocktimeを指定しているため、"locked"とprefixをつける。
locked_prev_txid      = 'a0e9516af706ccd50e3a3e38bb2b6ec1614fb2b698a5307acc5347ad6c306e2f'
locked_prev_out_index = 1
from_addr             = 'n4ZjDj9GArupgYd5fnCgHbQJYzw6W7EXsg'

# 1: P2PKH宛に送られたUTXO(1BTC)
# たくさんBTCを持つので、"rich"とprefixをつける。
rich_prev_txid        = '0d209395a78f375247610a33fae55b864c7281614e94f982dc1f2a421536edcc'
rich_prev_out_index   = 1
rich_addr             = 'muFzFv487hXbejWom1AfrmJB79ZWRGVLDK'

# 送金先(P2PKHのアドレス)
to = 'mxkgdvxkTyPQDzBHYFnSJhcsHM3qaEFmsP'

# Locktime:今回のP2SHのスクリプトで利用するもの。
locktime = 117

署名に使う鍵(秘密鍵と公開鍵)やRedeemScript(本体)をセット。

鍵の生成

アドレスから、秘密鍵を抽出する。

# P2SHのRedeemScriptが指定する公開鍵(のハッシュに対応するアドレス)を指定
from_priv_key = oa_api.provider.dumpprivkey(from_addr)
# => "cV3pVBgTCdZM2Hp3udWsMhS69HRXXYZZZfppb6Hmwu5xgMhtCMvT" 
from_key_obj  = Bitcoin::Key.from_base58(from_priv_key)
# => #<Bitcoin::Key:0x007fd66b924c50 @key=#<OpenSSL::PKey::EC:0x007fd66b924c00 @group=nil>, @pubkey_compressed=true> 

# P2PKHの鍵を抽出
rich_priv_key = oa_api.provider.dumpprivkey(rich_addr)
# => "cP3gdtj8ZWDLKaHekhySbYtBDDn97dRvyx9oKnc4mfbpUWUpYFqC" 
rich_key_obj  = Bitcoin::Key.from_base58(rich_priv_key)
# => #<Bitcoin::Key:0x007fd66a0e75e8 @key=#<OpenSSL::PKey::EC:0x007fd66a0e7598 @group=#<OpenSSL::PKey::EC::Group:0x007fd66a14f2d8 @key=#<OpenSSL::PKey::EC:0x007fd66a0e7598 ...>>>, @pubkey_compressed=true> 

ここで、P2SHアドレスは使わないことに注意。P2SHは、RedeemScriptのハッシュであって、公開鍵ハッシュではない。よって、これをもとに鍵を抽出することはできない。なので、RedeemScript内で指定される公開鍵に対応する秘密鍵を抽出する。
ここでは、そのアドレスを「from_addr」と呼ぶ。

UTXOの取得

txidが示すトランザクションを取得。

# P2SHのUTXOが含まれるトランザクション
locked_prev_tx_bin = oa_api.provider.getrawtransaction(locked_prev_txid)
# => "0100000007969eeff61091ccc9fdffe74e61d77bcce078d561e99b30d547731cdc4fd3894a000000006a473044022067d3c6fc4cd827ce29647590b89756f349a49bb5574155d0656dea3e715417ed022017f729d07f521b1c5ee6cd805f2889fe839db9abc297a8719f48bb6aa90317f901210385c92463fea97a458d60baf7270a4eb903f7c8927eddcb389af0c0599179058cffffffff969eeff61091ccc9fdffe74e61d77bcce078d561e99b30d547731cdc4fd3894a010000006a473044022047108df8e00b12d392947b6a3c0e96050f4340e3db1c3f2429a1ff0efa0a3fd1022071ca80e4c1706b364c9911f874880b76c85903b25e315f0917749001c54cc44601210385c92463fea97a458d60baf7270a4eb903f7c8927eddcb389af0c0599179058cffffffff969eeff61091ccc9fdffe74e61d77bcce078d561e99b30d547731cdc4fd3894a020000006a47304402206935d1902891f29318a5b8c80626f992e2106e532729413dcc5bc94cba68fade02202c831f182c7b10d13e3fc60cad47df997645a1a41fc1cc6c1e77cb22de565d9a01210385c92463fea97a458d60baf7270a4eb903f7c8927eddcb389af0c0599179058cffffffff969eeff61091ccc9fdffe74e61d77bcce078d561e99b30d547731cdc4fd3894a030000006a473044022015322b898dfde14cdc5b84e8c6dfb2b383236b57a85e36735b99fc84e8eb0cc7022066f5665b77241c3517d2e88a419fa5f64657c757f12299c85b86290689ba259801210385c92463fea97a458d60baf7270a4eb903f7c8927eddcb389af0c0599179058cffffffff969eeff61091ccc9fdffe74e61d77bcce078d561e99b30d547731cdc4fd3894a040000006b48304502210085e176d3bdbbad87791cc9419984d057083b39d48c9c43430d4db48adf51255e02204336ac890e2c53e1c716dac6c725a5632c73c3fa67e5144e04d2d342eded836701210385c92463fea97a458d60baf7270a4eb903f7c8927eddcb389af0c0599179058cffffffff969eeff61091ccc9fdffe74e61d77bcce078d561e99b30d547731cdc4fd3894a050000006b483045022100b7cac45a8dfe0b2eb4d190743d995eaac02baa31b4ef35e50b6730e451a27acf0220630edd8d67eb835204141023da734ba8da161a5765ce3c64f480665b9471fac401210385c92463fea97a458d60baf7270a4eb903f7c8927eddcb389af0c0599179058cffffffff38a425c047b095e52b6a78f4cc1407279ddc1f8821d732c5fcbb6099b8e96740010000006b483045022100d4c29b327bd153d45a5546aed214cce6ac31d0d61e21d2b1dba30626187a6f02022059f4076ad8f722fb3655eac159f279943a01f03e7b0d7c6809d6f183cb584f9701210385c92463fea97a458d60baf7270a4eb903f7c8927eddcb389af0c0599179058cffffffff080000000000000000146a124f41010006cf03cf03cf03cf03cf03cf0300580200000000000017a914e0917e2b61266ebffcdd931985bf32431409a71887580200000000000017a914f11890e00326c39e91ac4eb63963305b7cc5f0d287580200000000000017a914c1d5eb5b0d3bcfb281e8b4c2b2d500543c7696a587580200000000000017a9145dc04da167645f9e333a2ededf09c399abba279d87580200000000000017a9143b4afd0231eb35a5745a3dd694848c177ddda3e287580200000000000017a914df86617a5d92ef50287d26faca01e3350147b9c187f0b9f505000000001976a914de7ff8730cf8a5533a1e794f214289e5d5fe1e5388ac00000000" 
locked_prev_tx     = Bitcoin::Protocol::Tx.new(locked_prev_tx_bin.htb)
# => #<Bitcoin::Protocol::Tx:0x007fd66a01d810 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007fd66a01c550 @value=0, @pk_script_length=20, @pk_script="j\x12OA\x01\x00\x06\xCF\x03\xCF\x03\xCF\x03\xCF\x03\xCF\x03\xCF\x03\x00">, #<Bitcoin::Protocol::TxOut:0x007fd66a01c3c0 @value=600, @pk_script_length=23, @pk_script="\xA9\x14\xE0\x91~+a&n\xBF\xFC\xDD\x93\x19\x85\xBF2C\x14\t\xA7\x18\x87">, #<Bitcoin::Protocol::TxOut:0x007fd66a01c208 @value=600, @pk_script_length=23, @pk_script="\xA9\x14\xF1\x18\x90\xE0\x03&\xC3\x9E\x91\xACN\xB69c0[|\xC5\xF0\xD2\x87">, #<Bitcoin::Protocol::TxOut:0x007fd66a01c028 @value=600, @pk_script_length=23, @pk_script="\xA9\x14\xC1\xD5\xEB[\r;\xCF\xB2\x81\xE8\xB4\xC2\xB2\xD5\x00T<v\x96\xA5\x87">, #<Bitcoin::Protocol::TxOut:0x007fd66a017e38 @value=600, @pk_script_length=23, @pk_script="\xA9\x14]\xC0M\xA1gd_\x9E3:.\xDE\xDF\t\xC3\x99\xAB\xBA'\x9D\x87">, #<Bitcoin::Protocol::TxOut:0x007fd66a017c58 @value=600, @pk_script_length=23, @pk_script="\xA9\x14;J\xFD\x021\xEB5\xA5tZ=\xD6\x94\x84\x8C\x17}\xDD\xA3\xE2\x87">, #<Bitcoin::Protocol::TxOut:0x007fd66a017ac8 @value=600, @pk_script_length=23, @pk_script="\xA9\x14\xDF\x86az]\x92\xEFP(}&\xFA\xCA\x01\xE35\x01G\xB9\xC1\x87">, #<Bitcoin::Protocol::TxOut:0x007fd66a017938 @value=99990000, @pk_script_length=25, @pk_script="v\xA9\x14\xDE\x7F\xF8s\f\xF8\xA5S:\x1EyO!B\x89\xE5\xD5\xFE\x1ES\x88\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007fd66a01d608 @prev_out_hash="\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J", @prev_out_index=0, @script_sig_length=106, @script_sig="G0D\x02 g\xD3\xC6\xFCL\xD8'\xCE)du\x90\xB8\x97V\xF3I\xA4\x9B\xB5WAU\xD0em\xEA>qT\x17\xED\x02 \x17\xF7)\xD0\x7FR\e\x1C^\xE6\xCD\x80_(\x89\xFE\x83\x9D\xB9\xAB\xC2\x97\xA8q\x9FH\xBBj\xA9\x03\x17\xF9\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C", @sequence="\xFF\xFF\xFF\xFF">, #<Bitcoin::Protocol::TxIn:0x007fd66a01d3d8 @prev_out_hash="\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J", @prev_out_index=1, @script_sig_length=106, @script_sig="G0D\x02 G\x10\x8D\xF8\xE0\v\x12\xD3\x92\x94{j<\x0E\x96\x05\x0FC@\xE3\xDB\x1C?$)\xA1\xFF\x0E\xFA\n?\xD1\x02 q\xCA\x80\xE4\xC1pk6L\x99\x11\xF8t\x88\vv\xC8Y\x03\xB2^1_\t\x17t\x90\x01\xC5L\xC4F\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C", @sequence="\xFF\xFF\xFF\xFF">, #<Bitcoin::Protocol::TxIn:0x007fd66a01d1a8 @prev_out_hash="\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J", @prev_out_index=2, @script_sig_length=106, @script_sig="G0D\x02 i5\xD1\x90(\x91\xF2\x93\x18\xA5\xB8\xC8\x06&\xF9\x92\xE2\x10nS')A=\xCC[\xC9L\xBAh\xFA\xDE\x02 ,\x83\x1F\x18,{\x10\xD1>?\xC6\f\xADG\xDF\x99vE\xA1\xA4\x1F\xC1\xCCl\x1Ew\xCB\"\xDEV]\x9A\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C", @sequence="\xFF\xFF\xFF\xFF">, #<Bitcoin::Protocol::TxIn:0x007fd66a01cfa0 @prev_out_hash="\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J", @prev_out_index=3, @script_sig_length=106, @script_sig="G0D\x02 \x152+\x89\x8D\xFD\xE1L\xDC[\x84\xE8\xC6\xDF\xB2\xB3\x83#kW\xA8^6s[\x99\xFC\x84\xE8\xEB\f\xC7\x02 f\xF5f[w$\x1C5\x17\xD2\xE8\x8AA\x9F\xA5\xF6FW\xC7W\xF1\"\x99\xC8[\x86)\x06\x89\xBA%\x98\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C", @sequence="\xFF\xFF\xFF\xFF">, #<Bitcoin::Protocol::TxIn:0x007fd66a01cd98 @prev_out_hash="\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J", @prev_out_index=4, @script_sig_length=107, @script_sig="H0E\x02!\x00\x85\xE1v\xD3\xBD\xBB\xAD\x87y\x1C\xC9A\x99\x84\xD0W\b;9\xD4\x8C\x9CCC\rM\xB4\x8A\xDFQ%^\x02 C6\xAC\x89\x0E,S\xE1\xC7\x16\xDA\xC6\xC7%\xA5c,s\xC3\xFAg\xE5\x14N\x04\xD2\xD3B\xED\xED\x83g\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C", @sequence="\xFF\xFF\xFF\xFF">, #<Bitcoin::Protocol::TxIn:0x007fd66a01caf0 @prev_out_hash="\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J", @prev_out_index=5, @script_sig_length=107, @script_sig="H0E\x02!\x00\xB7\xCA\xC4Z\x8D\xFE\v.\xB4\xD1\x90t=\x99^\xAA\xC0+\xAA1\xB4\xEF5\xE5\vg0\xE4Q\xA2z\xCF\x02 c\x0E\xDD\x8Dg\xEB\x83R\x04\x14\x10#\xDAsK\xA8\xDA\x16\x1AWe\xCE<d\xF4\x80f[\x94q\xFA\xC4\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C", @sequence="\xFF\xFF\xFF\xFF">, #<Bitcoin::Protocol::TxIn:0x007fd66a01c898 @prev_out_hash="8\xA4%\xC0G\xB0\x95\xE5+jx\xF4\xCC\x14\a'\x9D\xDC\x1F\x88!\xD72\xC5\xFC\xBB`\x99\xB8\xE9g@", @prev_out_index=1, @script_sig_length=107, @script_sig="H0E\x02!\x00\xD4\xC2\x9B2{\xD1S\xD4ZUF\xAE\xD2\x14\xCC\xE6\xAC1\xD0\xD6\x1E!\xD2\xB1\xDB\xA3\x06&\x18zo\x02\x02 Y\xF4\aj\xD8\xF7\"\xFB6U\xEA\xC1Y\xF2y\x94:\x01\xF0>{\r|h\t\xD6\xF1\x83\xCBXO\x97\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C", @sequence="\xFF\xFF\xFF\xFF">], @lock_time=0, @ver=1, @enable_bitcoinconsensus=false, @payload="\x01\x00\x00\x00\a\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J\x00\x00\x00\x00jG0D\x02 g\xD3\xC6\xFCL\xD8'\xCE)du\x90\xB8\x97V\xF3I\xA4\x9B\xB5WAU\xD0em\xEA>qT\x17\xED\x02 \x17\xF7)\xD0\x7FR\e\x1C^\xE6\xCD\x80_(\x89\xFE\x83\x9D\xB9\xAB\xC2\x97\xA8q\x9FH\xBBj\xA9\x03\x17\xF9\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C\xFF\xFF\xFF\xFF\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J\x01\x00\x00\x00jG0D\x02 G\x10\x8D\xF8\xE0\v\x12\xD3\x92\x94{j<\x0E\x96\x05\x0FC@\xE3\xDB\x1C?$)\xA1\xFF\x0E\xFA\n?\xD1\x02 q\xCA\x80\xE4\xC1pk6L\x99\x11\xF8t\x88\vv\xC8Y\x03\xB2^1_\t\x17t\x90\x01\xC5L\xC4F\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C\xFF\xFF\xFF\xFF\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J\x02\x00\x00\x00jG0D\x02 i5\xD1\x90(\x91\xF2\x93\x18\xA5\xB8\xC8\x06&\xF9\x92\xE2\x10nS')A=\xCC[\xC9L\xBAh\xFA\xDE\x02 ,\x83\x1F\x18,{\x10\xD1>?\xC6\f\xADG\xDF\x99vE\xA1\xA4\x1F\xC1\xCCl\x1Ew\xCB\"\xDEV]\x9A\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C\xFF\xFF\xFF\xFF\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J\x03\x00\x00\x00jG0D\x02 \x152+\x89\x8D\xFD\xE1L\xDC[\x84\xE8\xC6\xDF\xB2\xB3\x83#kW\xA8^6s[\x99\xFC\x84\xE8\xEB\f\xC7\x02 f\xF5f[w$\x1C5\x17\xD2\xE8\x8AA\x9F\xA5\xF6FW\xC7W\xF1\"\x99\xC8[\x86)\x06\x89\xBA%\x98\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C\xFF\xFF\xFF\xFF\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J\x04\x00\x00\x00kH0E\x02!\x00\x85\xE1v\xD3\xBD\xBB\xAD\x87y\x1C\xC9A\x99\x84\xD0W\b;9\xD4\x8C\x9CCC\rM\xB4\x8A\xDFQ%^\x02 C6\xAC\x89\x0E,S\xE1\xC7\x16\xDA\xC6\xC7%\xA5c,s\xC3\xFAg\xE5\x14N\x04\xD2\xD3B\xED\xED\x83g\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C\xFF\xFF\xFF\xFF\x96\x9E\xEF\xF6\x10\x91\xCC\xC9\xFD\xFF\xE7Na\xD7{\xCC\xE0x\xD5a\xE9\x9B0\xD5Gs\x1C\xDCO\xD3\x89J\x05\x00\x00\x00kH0E\x02!\x00\xB7\xCA\xC4Z\x8D\xFE\v.\xB4\xD1\x90t=\x99^\xAA\xC0+\xAA1\xB4\xEF5\xE5\vg0\xE4Q\xA2z\xCF\x02 c\x0E\xDD\x8Dg\xEB\x83R\x04\x14\x10#\xDAsK\xA8\xDA\x16\x1AWe\xCE<d\xF4\x80f[\x94q\xFA\xC4\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C\xFF\xFF\xFF\xFF8\xA4%\xC0G\xB0\x95\xE5+jx\xF4\xCC\x14\a'\x9D\xDC\x1F\x88!\xD72\xC5\xFC\xBB`\x99\xB8\xE9g@\x01\x00\x00\x00kH0E\x02!\x00\xD4\xC2\x9B2{\xD1S\xD4ZUF\xAE\xD2\x14\xCC\xE6\xAC1\xD0\xD6\x1E!\xD2\xB1\xDB\xA3\x06&\x18zo\x02\x02 Y\xF4\aj\xD8\xF7\"\xFB6U\xEA\xC1Y\xF2y\x94:\x01\xF0>{\r|h\t\xD6\xF1\x83\xCBXO\x97\x01!\x03\x85\xC9$c\xFE\xA9zE\x8D`\xBA\xF7'\nN\xB9\x03\xF7\xC8\x92~\xDD\xCB8\x9A\xF0\xC0Y\x91y\x05\x8C\xFF\xFF\xFF\xFF\b\x00\x00\x00\x00\x00\x00\x00\x00\x14j\x12OA\x01\x00\x06\xCF\x03\xCF\x03\xCF\x03\xCF\x03\xCF\x03\xCF\x03\x00X\x02\x00\x00\x00\x00\x00\x00\x17\xA9\x14\xE0\x91~+a&n\xBF\xFC\xDD\x93\x19\x85\xBF2C\x14\t\xA7\x18\x87X\x02\x00\x00\x00\x00\x00\x00\x17\xA9\x14\xF1\x18\x90\xE0\x03&\xC3\x9E\x91\xACN\xB69c0[|\xC5\xF0\xD2\x87X\x02\x00\x00\x00\x00\x00\x00\x17\xA9\x14\xC1\xD5\xEB[\r;\xCF\xB2\x81\xE8\xB4\xC2\xB2\xD5\x00T<v\x96\xA5\x87X\x02\x00\x00\x00\x00\x00\x00\x17\xA9\x14]\xC0M\xA1gd_\x9E3:.\xDE\xDF\t\xC3\x99\xAB\xBA'\x9D\x87X\x02\x00\x00\x00\x00\x00\x00\x17\xA9\x14;J\xFD\x021\xEB5\xA5tZ=\xD6\x94\x84\x8C\x17}\xDD\xA3\xE2\x87X\x02\x00\x00\x00\x00\x00\x00\x17\xA9\x14\xDF\x86az]\x92\xEFP(}&\xFA\xCA\x01\xE35\x01G\xB9\xC1\x87\xF0\xB9\xF5\x05\x00\x00\x00\x00\x19v\xA9\x14\xDE\x7F\xF8s\f\xF8\xA5S:\x1EyO!B\x89\xE5\xD5\xFE\x1ES\x88\xAC\x00\x00\x00\x00", @hash="a0e9516af706ccd50e3a3e38bb2b6ec1614fb2b698a5307acc5347ad6c306e2f"> 

# P2PKHのUTXOが含まれるトランザクション
rich_prev_tx_bin   = oa_api.provider.getrawtransaction(rich_prev_txid)
# => "0100000002d11a536e32c4437cca45d03dd4c35a39c4f9fab074d7a77b7f87f1e789b3e307000000006b483045022100a83f304622fa30e39ec07e6cd234e9225e198918f226f133cae62fb8471d1fd502207d3ef2edd68b8509707ceac1a4ce7771777f210914b73c16b1adcc16d7de19670121035c4c07baacdcf5fd7631f1c27de29811bc86aea7c4bdfd583f397b669edff400feffffffa0443ede7c08f16cebe90b718dc13b32c51e487e295753e5a20109fa9d155aa3000000006b483045022100c846646f5e909af13f4e1f2a13f6b02ca876a25884cb03ed068d6f5f6dd955d9022012d7b773a36a1cce4106da57ccc3e95aac0bc7f927614791a0c460e820ae28870121026fab0d63cf13d6bc57ace182033115a3acf91b6aa70ca70cd7ff753edc9f97defeffffff021070f205000000001976a91466c9312a3564e241605f78f5806a4d9031c3888088ac00e1f505000000001976a91496bd29086134fdc13906eb846b826602a6e0b4bf88ac79000000"
rich_prev_tx       = Bitcoin::Protocol::Tx.new(rich_prev_tx_bin.htb)
# => #<Bitcoin::Protocol::Tx:0x007fd66d0ef538 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007fd66d0eee80 @value=99774480, @pk_script_length=25, @pk_script="v\xA9\x14f\xC91*5d\xE2A`_x\xF5\x80jM\x901\xC3\x88\x80\x88\xAC">, #<Bitcoin::Protocol::TxOut:0x007fd66d0eecf0 @value=100000000, @pk_script_length=25, @pk_script="v\xA9\x14\x96\xBD)\ba4\xFD\xC19\x06\xEB\x84k\x82f\x02\xA6\xE0\xB4\xBF\x88\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007fd66d0ef330 @prev_out_hash="\xD1\x1ASn2\xC4C|\xCAE\xD0=\xD4\xC3Z9\xC4\xF9\xFA\xB0t\xD7\xA7{\x7F\x87\xF1\xE7\x89\xB3\xE3\a", @prev_out_index=0, @script_sig_length=107, @script_sig="H0E\x02!\x00\xA8?0F\"\xFA0\xE3\x9E\xC0~l\xD24\xE9\"^\x19\x89\x18\xF2&\xF13\xCA\xE6/\xB8G\x1D\x1F\xD5\x02 }>\xF2\xED\xD6\x8B\x85\tp|\xEA\xC1\xA4\xCEwqw\x7F!\t\x14\xB7<\x16\xB1\xAD\xCC\x16\xD7\xDE\x19g\x01!\x03\\L\a\xBA\xAC\xDC\xF5\xFDv1\xF1\xC2}\xE2\x98\x11\xBC\x86\xAE\xA7\xC4\xBD\xFDX?9{f\x9E\xDF\xF4\x00", @sequence="\xFE\xFF\xFF\xFF">, #<Bitcoin::Protocol::TxIn:0x007fd66d0ef128 @prev_out_hash="\xA0D>\xDE|\b\xF1l\xEB\xE9\vq\x8D\xC1;2\xC5\x1EH~)WS\xE5\xA2\x01\t\xFA\x9D\x15Z\xA3", @prev_out_index=0, @script_sig_length=107, @script_sig="H0E\x02!\x00\xC8Fdo^\x90\x9A\xF1?N\x1F*\x13\xF6\xB0,\xA8v\xA2X\x84\xCB\x03\xED\x06\x8Do_m\xD9U\xD9\x02 \x12\xD7\xB7s\xA3j\x1C\xCEA\x06\xDAW\xCC\xC3\xE9Z\xAC\v\xC7\xF9'aG\x91\xA0\xC4`\xE8 \xAE(\x87\x01!\x02o\xAB\rc\xCF\x13\xD6\xBCW\xAC\xE1\x82\x031\x15\xA3\xAC\xF9\ej\xA7\f\xA7\f\xD7\xFFu>\xDC\x9F\x97\xDE", @sequence="\xFE\xFF\xFF\xFF">], @lock_time=121, @ver=1, @enable_bitcoinconsensus=false, @payload="\x01\x00\x00\x00\x02\xD1\x1ASn2\xC4C|\xCAE\xD0=\xD4\xC3Z9\xC4\xF9\xFA\xB0t\xD7\xA7{\x7F\x87\xF1\xE7\x89\xB3\xE3\a\x00\x00\x00\x00kH0E\x02!\x00\xA8?0F\"\xFA0\xE3\x9E\xC0~l\xD24\xE9\"^\x19\x89\x18\xF2&\xF13\xCA\xE6/\xB8G\x1D\x1F\xD5\x02 }>\xF2\xED\xD6\x8B\x85\tp|\xEA\xC1\xA4\xCEwqw\x7F!\t\x14\xB7<\x16\xB1\xAD\xCC\x16\xD7\xDE\x19g\x01!\x03\\L\a\xBA\xAC\xDC\xF5\xFDv1\xF1\xC2}\xE2\x98\x11\xBC\x86\xAE\xA7\xC4\xBD\xFDX?9{f\x9E\xDF\xF4\x00\xFE\xFF\xFF\xFF\xA0D>\xDE|\b\xF1l\xEB\xE9\vq\x8D\xC1;2\xC5\x1EH~)WS\xE5\xA2\x01\t\xFA\x9D\x15Z\xA3\x00\x00\x00\x00kH0E\x02!\x00\xC8Fdo^\x90\x9A\xF1?N\x1F*\x13\xF6\xB0,\xA8v\xA2X\x84\xCB\x03\xED\x06\x8Do_m\xD9U\xD9\x02 \x12\xD7\xB7s\xA3j\x1C\xCEA\x06\xDAW\xCC\xC3\xE9Z\xAC\v\xC7\xF9'aG\x91\xA0\xC4`\xE8 \xAE(\x87\x01!\x02o\xAB\rc\xCF\x13\xD6\xBCW\xAC\xE1\x82\x031\x15\xA3\xAC\xF9\ej\xA7\f\xA7\f\xD7\xFFu>\xDC\x9F\x97\xDE\xFE\xFF\xFF\xFF\x02\x10p\xF2\x05\x00\x00\x00\x00\x19v\xA9\x14f\xC91*5d\xE2A`_x\xF5\x80jM\x901\xC3\x88\x80\x88\xAC\x00\xE1\xF5\x05\x00\x00\x00\x00\x19v\xA9\x14\x96\xBD)\ba4\xFD\xC19\x06\xEB\x84k\x82f\x02\xA6\xE0\xB4\xBF\x88\xACy\x00\x00\x00", @hash="0d209395a78f375247610a33fae55b864c7281614e94f982dc1f2a421536edcc"> 

RedeemScriptの取得

RedeemScriptは、何らかの方法(メールや別のWebシステムなど)でもらっているものとする。

redeem_script_hex = '0175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac'

redeem_script     = Bitcoin::Script.new(redeem_script_hex.htb)
# => #<Bitcoin::Script:0x007fd66e091e28 @raw_byte_sizes=[39, 0], @previous_output_script=nil, @input_script="\x01u\xB1u!\x02\x05x q\x06w\xD9g\xD1;k\xE4\x04\xA2b$\x98\xCE\x0F\x84\xCB\xB1\a\xB9\x00?\x833zm\x01\e\xAC", @raw="\x01u\xB1u!\x02\x05x q\x06w\xD9g\xD1;k\xE4\x04\xA2b$\x98\xCE\x0F\x84\xCB\xB1\a\xB9\x00?\x833zm\x01\e\xAC", @chunks=["u", 177, 117, "\x02\x05x q\x06w\xD9g\xD1;k\xE4\x04\xA2b$\x98\xCE\x0F\x84\xCB\xB1\a\xB9\x00?\x833zm\x01\e", 172], @exec_stack=[], @stack_alt=[], @stack=[], @last_codeseparator_index=0, @do_exec=true> 

# こんなスクリプト
redeem_script.to_string
# => "75 OP_NOP2 OP_DROP 02057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011b OP_CHECKSIG" 

hex形式だと、何かと受け渡ししやすいので、この形式でもらっていたと仮定。

スクリプトの解説を少しすると、

  • 「75」はリトルエンディアン形式のlocktime。値は117。
  • 「OP_NOP2」はOP_CHECKLOCKTIMEVERIFYのこと。
  • 「OP_DROP」はスタック最上位の要素を削除する命令。
  • 「020…」は受信者(そのUTXOが使用可能な人)の公開鍵(本体)。
  • 「OP_CHECKSIG」は、P2PKHでもおなじみのscriptSig(sig + pubkey)の検証命令。

未署名のトランザクション作成

P2PKHの作成

Builderクラスを使って生成。署名(scriptSig)の作成はしないので、input作成時には、signature_keyは指定しない。

tx_bldr = Bitcoin::Builder::TxBuilder.new

# 入力部の作成
tx_bldr.input do |i|
  i.prev_out rich_prev_tx
  i.prev_out_index rich_prev_out_index
end

# 出力部の作成
tx_out_bldr = tx_bldr.output do |o|
   o.value 99800000 # 0.998 BTC in satoshis
   o.script {|s| s.recipient to }
end

# こんな感じの未署名トランザクションができる
pp tx_bldr.tx.to_hash

{"hash"=>"1d3b74cd4933230dff2dbaa91aff119c863c695a225bff1ae2c3495b31d0bfdf",
 "ver"=>1,
 "vin_sz"=>1,
 "vout_sz"=>1,
 "lock_time"=>0,
 "size"=>85,
 "in"=>
  [{"prev_out"=>
     {"hash"=>
       "0d209395a78f375247610a33fae55b864c7281614e94f982dc1f2a421536edcc",
      "n"=>1},
    "scriptSig"=>""}],
 "out"=>
  [{"value"=>"0.99800000",
    "scriptPubKey"=>
     "OP_DUP OP_HASH160 bd12e4492be5b2c65b21cb38fad62851397c81f7 OP_EQUALVERIFY OP_CHECKSIG"}]}

P2SHの作成

Azuchi様のブログを参考に、トランザクション生成メソッドを定義して、よび出す。

# LOCKTIME付きP2SHのトランザクション生成メソッド
def spend_unsigned_tx(locktime, txid, vout, address)
  tx = Bitcoin::Protocol::Tx.new
  tx.lock_time = locktime   # トランザクション全体のlock_timeを指定する
  # 入力部の作成(これがP2SHで送られたUTXO)
  tx_in = Bitcoin::Protocol::TxIn.from_hex_hash(txid, vout)
  tx_in.sequence = [0].pack("V")   # ffffffffの場合、検証に失敗する。
  tx.add_in(tx_in)
  # 出力部の作成(600satoshi分の送金)
  tx_out = Bitcoin::Protocol::TxOut.new(600, Bitcoin::Script.new(Bitcoin::Script.to_address_script(address)).to_payload)
  tx.add_out(tx_out)
  tx
end

# 「凍結されたUTXO」を入力としたトランザクションを作成
unsigned_tx = spend_unsigned_tx(locktime, locked_prev_txid, locked_prev_out_index, to)

# こんな感じの未署名トランザクションができる。
pp unsigned_tx.to_hash

{"hash"=>"c04f5ebcccd6fc58e337eb9c0636e8337d90df923cb94c0a975beecdf5648f5d",
 "ver"=>1,
 "vin_sz"=>1,
 "vout_sz"=>1,
 "lock_time"=>117,
 "size"=>85,
 "in"=>
  [{"prev_out"=>
     {"hash"=>
       "a0e9516af706ccd50e3a3e38bb2b6ec1614fb2b698a5307acc5347ad6c306e2f",
      "n"=>1},
    "scriptSig"=>"",
    "sequence"=>0}],
 "out"=>
  [{"value"=>"0.00000600",
    "scriptPubKey"=>
     "OP_DUP OP_HASH160 bd12e4492be5b2c65b21cb38fad62851397c81f7 OP_EQUALVERIFY OP_CHECKSIG"}]}

P2PKHとP2SHの合体

“tx”という名前のトランザクションにする。
P2SHが0番に、P2PKHが1番の入力になるように、次のようにする。

unsigned_tx.add_in(tx_bldr.tx.in[0])
unsigned_tx.add_out(tx_bldr.tx.out[0])

tx = unsigned_tx

# こんな感じのトランザクションになる
pp tx.to_hash; nil
{"hash"=>"c04f5ebcccd6fc58e337eb9c0636e8337d90df923cb94c0a975beecdf5648f5d",
 "ver"=>1,
 "vin_sz"=>2,
 "vout_sz"=>2,
 "lock_time"=>117,
 "size"=>85,
 "in"=>
  [{"prev_out"=>
     {"hash"=>
       "a0e9516af706ccd50e3a3e38bb2b6ec1614fb2b698a5307acc5347ad6c306e2f",
      "n"=>1},
    "scriptSig"=>"",
    "sequence"=>0},
   {"prev_out"=>
     {"hash"=>
       "0d209395a78f375247610a33fae55b864c7281614e94f982dc1f2a421536edcc",
      "n"=>1},
    "scriptSig"=>""}],
 "out"=>
  [{"value"=>"0.00000600",
    "scriptPubKey"=>
     "OP_DUP OP_HASH160 bd12e4492be5b2c65b21cb38fad62851397c81f7 OP_EQUALVERIFY OP_CHECKSIG"},
   {"value"=>"0.99800000",
    "scriptPubKey"=>
     "OP_DUP OP_HASH160 bd12e4492be5b2c65b21cb38fad62851397c81f7 OP_EQUALVERIFY OP_CHECKSIG"}]}

vinが2つ未署名で。voutが2つある。

署名実行

ここがポイント。vinのscriptSigは、「「vinを含むトランザクションのハッシュ値」を秘密鍵①で暗号化したもの」と「①の対となる公開鍵(本体)」が格納される。前者は、このトランザクションが改竄されていないことを主張するも(かつ、UTXOが指定する公開鍵等によってその利用が正当であると検証できるもの)ので、後者は、のちに第三者がそれを検証するためのものである。

格納されている情報だけみると、P2PKHの場合は以下。

 scriptSig: <sig> <pubKey>

P2SHの場合は、以下。(ただし、RedeemScriptによってケースバイケース)

scriptSig: ..signatures... <serialized script>

P2PKH部分

# そのトランザクションのハッシュを計算する
# signature_hash_for_inputメソッドは、まず、scriptSigを""で入れ替えて、
# 指定したvin(第一引数で指定)のscriptSigに、公開鍵ハッシュを入れた
# 「仮トランザクション」についてハッシュを計算する。
# このとき、lock_timeやhash_type、vin/voutの数やペイロードもハッシュ計算に含まれるため、
# この署名以降で、vin/voutなどの変更はできない。
sig_hash = tx.signature_hash_for_input(1, rich_prev_tx,
  Bitcoin::Script::SIGHASH_TYPE[:all])
# => "-\xE30v@\x9A\x03\xDDt\xC7s\x00h;\x13\x91n\x91\x9D\fx\x88\xD8\x0F\x9C\xD3.\x89\x16\xA4e\x88" 

# 計算したハッシュを、秘密鍵で暗号化する。このときに、hash_typeを指定する。
# あとでvoutを変える必要がなければ、:allで指定する。
sig = rich_key_obj.sign(sig_hash) +
  [Bitcoin::Script::SIGHASH_TYPE[:all]].pack("C")
# => "0E\x02!\x00\xEF\xE74\x9Cy3\xBF\x8E\x80j\xF6F\xBC\xEB\xCE9\xDC\xEDH\xE4['\t\xC8A\xD2\x8C\n\x92\x9C\xD2o\x02 2p5\x9B\xA3,\x99\t[1\x917D\xFD\xDC\xF0-\xCB\xE8\xEA\rQY\xE1\xF4\xB54\x19N\x8F\xAA$\x01" 

# hexで見るとこんな感じ(tx.to_hashのscriptSigで確認できる値と同じ)
# sig.bth
# => "3045022100efe7349c7933bf8e806af646bcebce39dced48e45b2709c841d28c0a929cd26f02203270359ba32c99095b31913744fddcf02dcbe8ea0d5159e1f4b534194e8faa2401" 

# scriptSigを作成する。sigに加えて、それを第三者が検証するために公開鍵を付与する。
script_sig = Bitcoin::Script.new(Bitcoin::Script.pack_pushdata(sig) +
  Bitcoin::Script.pack_pushdata(rich_key_obj.pub.htb))
# => #<Bitcoin::Script:0x007fd66ae94560 @raw_byte_sizes=[107, 0], @previous_output_script=nil, @input_script="H0E\x02!\x00\xEF\xE74\x9Cy3\xBF\x8E\x80j\xF6F\xBC\xEB\xCE9\xDC\xEDH\xE4['\t\xC8A\xD2\x8C\n\x92\x9C\xD2o\x02 2p5\x9B\xA3,\x99\t[1\x917D\xFD\xDC\xF0-\xCB\xE8\xEA\rQY\xE1\xF4\xB54\x19N\x8F\xAA$\x01!\x02\x89_M\xA6AN\x946\x80fq\\E\xBC8\xC7\xFFK?\xB9|\xEC\xA7\x18$\x15\x88Q\x9C\x05I\x9B", @raw="H0E\x02!\x00\xEF\xE74\x9Cy3\xBF\x8E\x80j\xF6F\xBC\xEB\xCE9\xDC\xEDH\xE4['\t\xC8A\xD2\x8C\n\x92\x9C\xD2o\x02 2p5\x9B\xA3,\x99\t[1\x917D\xFD\xDC\xF0-\xCB\xE8\xEA\rQY\xE1\xF4\xB54\x19N\x8F\xAA$\x01!\x02\x89_M\xA6AN\x946\x80fq\\E\xBC8\xC7\xFFK?\xB9|\xEC\xA7\x18$\x15\x88Q\x9C\x05I\x9B", @chunks=["0E\x02!\x00\xEF\xE74\x9Cy3\xBF\x8E\x80j\xF6F\xBC\xEB\xCE9\xDC\xEDH\xE4['\t\xC8A\xD2\x8C\n\x92\x9C\xD2o\x02 2p5\x9B\xA3,\x99\t[1\x917D\xFD\xDC\xF0-\xCB\xE8\xEA\rQY\xE1\xF4\xB54\x19N\x8F\xAA$\x01", "\x02\x89_M\xA6AN\x946\x80fq\\E\xBC8\xC7\xFFK?\xB9|\xEC\xA7\x18$\x15\x88Q\x9C\x05I\x9B"], @exec_stack=[], @stack_alt=[], @stack=[], @last_codeseparator_index=0, @do_exec=true> 

# script_sig.to_string
# => 3045022100efe7349c7933bf8e806af646bcebce39dced48e45b2709c841d28c0a929cd26f02203270359ba32c99095b31913744fddcf02dcbe8ea0d5159e1f4b534194e8faa2401 02895f4da6414e94368066715c45bc38c7ff4b3fb97ceca718241588519c05499b" 

# 署名をセットする
tx.in[1].script_sig = script_sig.to_payload

私は、P2PKHはlocktimeは関係ない!と思い込んで、署名したあとに、
locktimeを変えたりしていたので、結構ハマりました。(後述)

P2SH部分

トランザクション作成時と同様に、専用のメソッドを用意。P2PKHと異なり、RedeemScript本体を使って署名を作る。(公開鍵の代わりになる)

# ロックされたUTXOの署名スクリプト(scriptSig)
def spend_script(priv_key, locktime, unsigned_tx, n, redeem_script)
  tx = Marshal.load(Marshal.dump(unsigned_tx))

  # vinのscript_sigに、RedeemScriptで仮置き(P2PKHのときは、自身の公開鍵ハッシュ)
  tx.in[0].script_sig = redeem_script.to_payload

  # 仮置き状態で、ハッシュ値を計算(P2PKHのときと同様)
  sig_hash = tx.signature_hash_for_input(n,
    redeem_script.to_payload, Bitcoin::Script::SIGHASH_TYPE[:all])

  # ハッシュ値を秘密鍵で暗号化。P2PKH同様、hash_typeは:allに合わせる。
  sig = priv_key.sign(sig_hash) +
    [Bitcoin::Script::SIGHASH_TYPE[:all]].pack("C")

  # 戻り値。スクリプト形式にして、sigとRedeemScript(本体)を送る。
  # 今回のRedeemScriptには、相手方の公開鍵(本体)が含まれるように作ってあるので、
  # P2PKHと同等の検証を行うことが可能。
  Bitcoin::Script.new(Bitcoin::Script.pack_pushdata(sig) +
    Bitcoin::Script.pack_pushdata(redeem_script.to_payload))
end

# メソッドでscriptSig作成。
spend_script = spend_script(from_key_obj, locktime, tx, 0, redeem_script)
# => #<Bitcoin::Script:0x007fd66d1bc4c0 @raw_byte_sizes=[112, 0], @previous_output_script=nil, @input_script="G0D\x02 ZO\xD0}\x19\xDF\x121\xF1\x03\xADx\xA1q\xDD\xCC\xAC#1\bcK\xF34\x19\x13\xE3^I\xB5h\xB4\x02 \x1D}\xFBI\x81\xA4\xA6\xBE\xD4 6\xE4\x95\xBD16\x83W\x9C\x99\xBF\xDF\xBD\x17\xD6}\xA6\xE1X\xF9\\#\x01'\x01u\xB1u!\x02\x05x q\x06w\xD9g\xD1;k\xE4\x04\xA2b$\x98\xCE\x0F\x84\xCB\xB1\a\xB9\x00?\x833zm\x01\e\xAC", @raw="G0D\x02 ZO\xD0}\x19\xDF\x121\xF1\x03\xADx\xA1q\xDD\xCC\xAC#1\bcK\xF34\x19\x13\xE3^I\xB5h\xB4\x02 \x1D}\xFBI\x81\xA4\xA6\xBE\xD4 6\xE4\x95\xBD16\x83W\x9C\x99\xBF\xDF\xBD\x17\xD6}\xA6\xE1X\xF9\\#\x01'\x01u\xB1u!\x02\x05x q\x06w\xD9g\xD1;k\xE4\x04\xA2b$\x98\xCE\x0F\x84\xCB\xB1\a\xB9\x00?\x833zm\x01\e\xAC", @chunks=["0D\x02 ZO\xD0}\x19\xDF\x121\xF1\x03\xADx\xA1q\xDD\xCC\xAC#1\bcK\xF34\x19\x13\xE3^I\xB5h\xB4\x02 \x1D}\xFBI\x81\xA4\xA6\xBE\xD4 6\xE4\x95\xBD16\x83W\x9C\x99\xBF\xDF\xBD\x17\xD6}\xA6\xE1X\xF9\\#\x01", "\x01u\xB1u!\x02\x05x q\x06w\xD9g\xD1;k\xE4\x04\xA2b$\x98\xCE\x0F\x84\xCB\xB1\a\xB9\x00?\x833zm\x01\e\xAC"], @exec_stack=[], @stack_alt=[], @stack=[], @last_codeseparator_index=0, @do_exec=true> 

# spend_script.to_string 
# => "304402205a4fd07d19df1231f103ad78a171ddccac233108634bf3341913e35e49b568b402201d7dfb4981a4a6bed42036e495bd313683579c99bfdfbd17d67da6e158f95c2301 0175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac" 

# 署名をセット
tx.in[0].script_sig = spend_script.to_payload

署名検証

verify_input_signatureが便利。

  • 第一引数: vinの番号
  • 第二引数: UTXOが含まれるトランザクション
# P2SHのUTXOを使うvin
tx.verify_input_signature(0, locked_prev_tx)
# => true

# P2PKHのUTXOを使うvin
tx.verify_input_signature(1, rich_prev_tx)
# => true

falseが表示されるときは、どこか間違いがある。
trueと表示されても、安心は禁物。
bitcoin-rubyはscriptの実装がやや遅く、例えば、今回例示のOP_NOP2は、何も処理がかかれていない。(locktimeの値が変わったとしても、常にtrueになる)

完成したトランザクション

pp tx.to_hash

{"hash"=>"1f82c103226a2a49d7e1c06b8484e8914ae88de4267643aa963f553af71d12a9",
 "ver"=>1,
 "vin_sz"=>2,
 "vout_sz"=>2,
 "lock_time"=>117,
 "size"=>378,
 "in"=>
  [{"prev_out"=>
     {"hash"=>
       "a0e9516af706ccd50e3a3e38bb2b6ec1614fb2b698a5307acc5347ad6c306e2f",
      "n"=>1},
    "scriptSig"=>
     "304402205a4fd07d19df1231f103ad78a171ddccac233108634bf3341913e35e49b568b402201d7dfb4981a4a6bed42036e495bd313683579c99bfdfbd17d67da6e158f95c2301 0175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac",
    "sequence"=>0},
   {"prev_out"=>
     {"hash"=>
       "0d209395a78f375247610a33fae55b864c7281614e94f982dc1f2a421536edcc",
      "n"=>1},
    "scriptSig"=>
     "30440220258cd9031851a454756ef1bd61226231f0b554e39e9efb77a119d1df9fbc955c02203eada6fa9e2ea0339a39229b8e5bea71acc080d9dfc9c6b3842d4a7447674d8201 02895f4da6414e94368066715c45bc38c7ff4b3fb97ceca718241588519c05499b"}],
 "out"=>
  [{"value"=>"0.00000600",
    "scriptPubKey"=>
     "OP_DUP OP_HASH160 bd12e4492be5b2c65b21cb38fad62851397c81f7 OP_EQUALVERIFY OP_CHECKSIG"},
   {"value"=>"0.99800000",
    "scriptPubKey"=>
     "OP_DUP OP_HASH160 bd12e4492be5b2c65b21cb38fad62851397c81f7 OP_EQUALVERIFY OP_CHECKSIG"}]}

実行

送金実行

serialzed_tx = tx.to_payload.bth
# => "01000000022f6e306cad4753cc7a30a598b6b24f61c16e2bbb383e3a0ed5cc06f76a51e9a0010000007047304402205a4fd07d19df1231f103ad78a171ddccac233108634bf3341913e35e49b568b402201d7dfb4981a4a6bed42036e495bd313683579c99bfdfbd17d67da6e158f95c2301270175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac00000000cced3615422a1fdc82f9944e6181724c865be5fa330a614752378fa79593200d010000006a4730440220258cd9031851a454756ef1bd61226231f0b554e39e9efb77a119d1df9fbc955c02203eada6fa9e2ea0339a39229b8e5bea71acc080d9dfc9c6b3842d4a7447674d82012102895f4da6414e94368066715c45bc38c7ff4b3fb97ceca718241588519c05499bffffffff0258020000000000001976a914bd12e4492be5b2c65b21cb38fad62851397c81f788acc0d3f205000000001976a914bd12e4492be5b2c65b21cb38fad62851397c81f788ac75000000" 

# 送信
oa_api.provider.sendrawtransaction(serialzed_tx)
# => "1f82c103226a2a49d7e1c06b8484e8914ae88de4267643aa963f553af71d12a9" 

確認

bitcoin-cliから、先ほどのtxidで検索してみる。

% bitcoin-cli getrawtransaction  1f82c103226a2a49d7e1c06b8484e8914ae88de4267643aa963f553af71d12a9 1
{
  "hex": "01000000022f6e306cad4753cc7a30a598b6b24f61c16e2bbb383e3a0ed5cc06f76a51e9a0010000007047304402205a4fd07d19df1231f103ad78a171ddccac233108634bf3341913e35e49b568b402201d7dfb4981a4a6bed42036e495bd313683579c99bfdfbd17d67da6e158f95c2301270175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac00000000cced3615422a1fdc82f9944e6181724c865be5fa330a614752378fa79593200d010000006a4730440220258cd9031851a454756ef1bd61226231f0b554e39e9efb77a119d1df9fbc955c02203eada6fa9e2ea0339a39229b8e5bea71acc080d9dfc9c6b3842d4a7447674d82012102895f4da6414e94368066715c45bc38c7ff4b3fb97ceca718241588519c05499bffffffff0258020000000000001976a914bd12e4492be5b2c65b21cb38fad62851397c81f788acc0d3f205000000001976a914bd12e4492be5b2c65b21cb38fad62851397c81f788ac75000000",
  "txid": "1f82c103226a2a49d7e1c06b8484e8914ae88de4267643aa963f553af71d12a9",
  "size": 378,
  "version": 1,
  "locktime": 117,
  "vin": [
    {
      "txid": "a0e9516af706ccd50e3a3e38bb2b6ec1614fb2b698a5307acc5347ad6c306e2f",
      "vout": 1,
      "scriptSig": {
        "asm": "304402205a4fd07d19df1231f103ad78a171ddccac233108634bf3341913e35e49b568b402201d7dfb4981a4a6bed42036e495bd313683579c99bfdfbd17d67da6e158f95c23[ALL] 0175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac",
        "hex": "47304402205a4fd07d19df1231f103ad78a171ddccac233108634bf3341913e35e49b568b402201d7dfb4981a4a6bed42036e495bd313683579c99bfdfbd17d67da6e158f95c2301270175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac"
      },
      "sequence": 0
    }, 
    {
      "txid": "0d209395a78f375247610a33fae55b864c7281614e94f982dc1f2a421536edcc",
      "vout": 1,
      "scriptSig": {
        "asm": "30440220258cd9031851a454756ef1bd61226231f0b554e39e9efb77a119d1df9fbc955c02203eada6fa9e2ea0339a39229b8e5bea71acc080d9dfc9c6b3842d4a7447674d82[ALL] 02895f4da6414e94368066715c45bc38c7ff4b3fb97ceca718241588519c05499b",
        "hex": "4730440220258cd9031851a454756ef1bd61226231f0b554e39e9efb77a119d1df9fbc955c02203eada6fa9e2ea0339a39229b8e5bea71acc080d9dfc9c6b3842d4a7447674d82012102895f4da6414e94368066715c45bc38c7ff4b3fb97ceca718241588519c05499b"
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.00000600,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 bd12e4492be5b2c65b21cb38fad62851397c81f7 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914bd12e4492be5b2c65b21cb38fad62851397c81f788ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mxkgdvxkTyPQDzBHYFnSJhcsHM3qaEFmsP"
        ]
      }
    }, 
    {
      "value": 0.99800000,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 bd12e4492be5b2c65b21cb38fad62851397c81f7 OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914bd12e4492be5b2c65b21cb38fad62851397c81f788ac",
        "reqSigs": 1,
        "type": "pubkeyhash",
        "addresses": [
          "mxkgdvxkTyPQDzBHYFnSJhcsHM3qaEFmsP"
        ]
      }
    }
  ],
  "blockhash": "26a33829645c4ba52b83da8427022307ee36a9a3fa2c30591cc462d4de2d8518",
  "confirmations": 1,
  "time": 1467771445,
  "blocktime": 1467771445
}

無事、ブロードキャストされました。めでたしめでたし。

発展:もっと詳しく

Tx.signature_hash_for_input

シグネチャ作成を担っているこれを見る。

      def signature_hash_for_input(input_idx, subscript, hash_type=nil)
        # 中略。pinは、"pseudo vin"(擬似vin)と思われる。TCP/IPの擬似ヘッダと同じ。
        # ハッシュ計算のためだけに作られるvin。scriptSigが空になり、
        # 指定のvinのscriptSigだけが、公開鍵ハッシュを埋められる。
        pin  = @in.map.with_index{|input,idx|
          if idx == input_idx
            subscript = subscript.out[ input.prev_out_index ].script if subscript.respond_to?(:out) # legacy api (outpoint_tx)
            input.to_payload(subscript)
          else
            case (hash_type & 0x1f)
            when SIGHASH_TYPE[:none];   input.to_payload("", "\x00\x00\x00\x00")
            when SIGHASH_TYPE[:single]; input.to_payload("", "\x00\x00\x00\x00")
            else;                       input.to_payload("")
            end
          end
        }

        pout = @out.map(&:to_payload)
        in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)

        case (hash_type & 0x1f)
        when SIGHASH_TYPE[:none]
          pout = ""
          out_size = Protocol.pack_var_int(0)
        when SIGHASH_TYPE[:single]
          return "\x01".ljust(32, "\x00") if input_idx >= @out.size # ERROR: SignatureHash() : input_idx=%d out of range (SIGHASH_SINGLE)
          pout = @out[0...(input_idx+1)].map.with_index{|out,idx| (idx==input_idx) ? out.to_payload : out.to_null_payload }.join
          out_size = Protocol.pack_var_int(input_idx+1)
        end

        if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
          in_size, pin = Protocol.pack_var_int(1), [ pin[input_idx] ]
        end

        # ここで、ハッシュ計算機にかける変数が定義される。@lock_timeが含まれる。
        buf = [ [@ver].pack("V"), in_size, pin, out_size, pout, [@lock_time, hash_type].pack("VV") ].join
        # ハッシュは二重で計算される。(そういう決まり)
        Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
      end

Tx.verify_input_signature

署名検証のメソッドを見る。一部割愛。

      def verify_input_signature(in_idx, outpoint_tx_or_script, block_timestamp=Time.now.to_i, opts={})
        if @enable_bitcoinconsensus
          return bitcoinconsensus_verify_script(in_idx, outpoint_tx_or_script, block_timestamp, opts)
        end

        outpoint_idx  = @in[in_idx].prev_out_index
        script_sig    = @in[in_idx].script_sig

        # outpoint_tx_or_scriptは、通常、UTXOを含むトランザクションが入っている。
        # script_pubkeyは、そのUTXOがもつscript_pubkeyである。         
        # If given an entire previous transaction, take the script from it
        script_pubkey = if outpoint_tx_or_script.respond_to?(:out) 
          outpoint_tx_or_script.out[outpoint_idx].pk_script
        else
          # Otherwise, it's already a script.
          outpoint_tx_or_script
        end

        # 検証用のスクリプトを作る。ここでいう検証とは、
        # UTXOで指定されたscriptPubKeyと、自身が作成したvinの
        # scriptSigを合わせたスクリプトが正しく動作するかをみている。
        # 割符検証のようなことをしている。
        @scripts[in_idx] = Bitcoin::Script.new(script_sig, script_pubkey)
        # ここが実行部分。
        # do~end句は、OP_CKECKSIGが実行されたときにコールバックされるメソッド。
        # OP_CHECKSIG実行時に、ハッシュを作成し、それが公開鍵で対応開けるかどうか検証している。
        sig_valid = @scripts[in_idx].run(block_timestamp, opts) do |pubkey,sig,hash_type,subscript|
          hash = signature_hash_for_input(in_idx, subscript, hash_type)
          Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
        end

        return sig_valid
      end

この辺りは、スクリプトの説明の回で詳しく説明予定。

あとがき

署名系のライブラリは充実していることがわかりましたが、もう少しハイレベルなところから署名できるようになるといいですね。

署名の仕組みを知らないと、ライブラリが使いこなせず、結果、複雑なトランザクションが作れない現状は、初心者にはハードルが高く感じます。

参考

Bitcoin::Builderを使ってトランザクションを作る
http://qiita.com/osada/items/edb1f0681735586ca586

Transactionの説明(公式サイト)
https://en.bitcoin.it/wiki/Transaction#Pay-to-PubkeyHash


『 Bitcoin 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。