環境
bitcoind v0.16
ruby v2.5
bitcoin-ruby v0.0.18
これやる
ライブラリに頼らずに、bitcoin transactionをつくる方法。
p2pkhのoutputを消費します。
鍵と署名に関してはライブラリを使います。
トランザクションの構造と署名のつくりかたを主題とするので、scriptのしくみなどは省きます。
inputは1つだけのシンプルなものです。
基本的なことはやはりmastering bitcoin/ transactionがわかりやすいです。ご参考ください。
opコードのパースにはbtcdebを使わせていただきます。
注意点
数値はlittle endianで表記。
トランザクションの構造
トランザクションの構造に関しては、こちらの記事がとても参考になります。
BitcoinのTransaction
Bitcoin Standard Transactions(送信)
- その他参考
Transaction/Bitcoin Wiki
トランザクションの例
0100000001dbfb811cacf55c78a49e454b017cfc10f34382c51ed98859c119aa261ba3f661000000006a47304402201ff7410c71653ab547a7ace6667f8262702d392b496c7385619f4655e5d2180d02202597db2cdd1243602f7537285c8eda21952dd2afd4c6c941f566bddc7b75d6bc01210390fc7c6a9747091ac4c94721887c7d8ccecd9ddc02aaae005138689d5c93a3affffffff0200301a1e010000001976a9144ae221ba1282464e44cd8217835fb44e92030ab288ac605af405000000001976a914d3da933dc800e1d0b2645bd3fa2616af758f571188ac00000000
トランザクション(tx)を組み立てる
p2pkhのoutputを消費するtxを作ります。
msvkJkuUpcH51VodGDXtv48z58Y1udgUKb
に20BTC送ります。
tx_inにはsignature script部分があります。
これは署名を含みます。
なにに対して署名をするかは、署名するユーザが選択することができますが、今回はスタンダードなtx全体に署名する方式(sighash all)を使います。
input(tx_in)の作成
ここではinputのもととなるトランザクションをbitcoindでつくります。
生成されるアドレスやトランザクションはここでの例とまったく同じにはなりません。
tx_in作成の準備
通常のアドレスを取得する
$ bitcoin-cli getnewaddress '' legacy
mkNBR5PyMTdhWtKopfSeXigS1p7QAD8f4h
作ったアドレスに30BTC送る。戻り値はトランザクションid
$ bitcoin-cli sendtoaddress mkNBR5PyMTdhWtKopfSeXigS1p7QAD8f4h 30
d651820a8718f83ae97fe5e6554798c069868a172477909a92460ca27fd7aa2f
トランザクションの内容を詳しく見る(最後に1を付け加えるのを忘れずに 1)
$ bitcoin-cli getrawtransaction d651820a8718f83ae97fe5e6554798c069868a172477909a92460ca27fd7aa2f 1
{
"txid": "d651820a8718f83ae97fe5e6554798c069868a172477909a92460ca27fd7aa2f",
"hash": "d651820a8718f83ae97fe5e6554798c069868a172477909a92460ca27fd7aa2f",
"version": 2,
"size": 190,
"vsize": 190,
"weight": 760,
"locktime": 113,
"vin": [
{
"txid": "56f8d8837aceb14533b42df6f76dd5eaabffdf355d8f7fb3747b7c7568b5d6b3",
"vout": 0,
"scriptSig": {
"asm": "3045022100e4410a4662ff1649c26df4c8ce76e8d9e105c082ce682558d933c9f1c263823e02206467ddb539c3f498da324eec0856071356e12f04efeb76c467330aefcc66bec0[ALL]",
"hex": "483045022100e4410a4662ff1649c26df4c8ce76e8d9e105c082ce682558d933c9f1c263823e02206467ddb539c3f498da324eec0856071356e12f04efeb76c467330aefcc66bec001"
},
"sequence": 4294967294
}
],
"vout": [
{
"value": 30.00000000,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 352fd6937e24d9de927b857e5d8ea9e3a8fe8f91 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914352fd6937e24d9de927b857e5d8ea9e3a8fe8f9188ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"mkNBR5PyMTdhWtKopfSeXigS1p7QAD8f4h"
]
}
},
{
"value": 19.99996200,
"n": 1,
"scriptPubKey": {
"asm": "OP_HASH160 29ab892dc58556e7e0e8bd213e13e5b637d29bc3 OP_EQUAL",
"hex": "a91429ab892dc58556e7e0e8bd213e13e5b637d29bc387",
"reqSigs": 1,
"type": "scripthash",
"addresses": [
"2Mw3ZBfmCUrBzBdoKtaBp8rzYr7Qtgy3TmS"
]
}
}
],
"hex": "0200000001b3d6b568757c7b74b37f8f5d35dfffabead56df7f62db43345b1ce7a83d8f8560000000049483045022100e4410a4662ff1649c26df4c8ce76e8d9e105c082ce682558d933c9f1c263823e02206467ddb539c3f498da324eec0856071356e12f04efeb76c467330aefcc66bec001feffffff02005ed0b2000000001976a914352fd6937e24d9de927b857e5d8ea9e3a8fe8f9188ac288535770000000017a91429ab892dc58556e7e0e8bd213e13e5b637d29bc38771000000"
}
tx_inを組み立てる
voutの1つ目をinputとして消費します。
previous_output
これはoutpoint
という型で、32 Byteのprevious transaction id(hash) + 4 Byteのoutput indexです2。
previous transaction idはもととなるoutputが含まれるtransactionのidですので
d651820a8718f83ae97fe5e6554798c069868a172477909a92460ca27fd7aa2f
ただしlittle endianにするのでトランザクション内では次のように表記されます。
2faad77fa20c46929a907724178a8669c0984755e6e57fe93af818870a8251d6
output indexはoutputのうちなかで何番目かということです。ここでは、n:0の30BTC分のoutputを利用するので
0x00000000
となります。
もし1の場合はlittle endienに注意して
0x01000000
となります。
script size
まだスクリプトのサイズはわからないので保留です。
signature script(unlocking script)
署名がここに含まれるのですが、tx全体に対して署名をしたいです(sighash all)。
署名するときにはまだ存在しない署名欄(まさにこの部分=script signature)はどうすればよいのでしょうか3。
p2pkhの場合、前トランザクションのscript pubkeyを一時的に当てはめておきます。
署名をするには、txの他の部分(tx_outなど)を組み立てる必要がありますので、tx_outも組み立ててから署名をしていきます。
sequence
通常ffffffff
の4Byteです。
いまのところffffffff
以外について私はわかりません。
outputの作成
value
送金量です。satoshiで表現し8Byteの16進数のlittle endienです。
20BTC送るので
0094357700000000
pubkey script size
pubkey script(次項目)のバイト長は25 Byteですので、16進数にして
19
pubkey script
今回はp2pkh宛で、以下のようにします。
76a91488217c4b26f9221e162fd43a919759e30069db3788ac
opコードにすると(by btcdeb)
script
-----------------------------------------
OP_DUP
OP_HASH160
88217c4b26f9221e162fd43a919759e30069db37
OP_EQUALVERIFY
OP_CHECKSIG
tx全体
これまでつくったtx_inとtx_outをつかって完全な形式に整えます。
version: 01000000
tx_in_count: 01
(tx_in)
(outpoint)
prev_txid: 2faad77fa20c46929a907724178a8669c0984755e6e57fe93af818870a8251d6
prev_output_index: 00000000
script_size: 19
script_sig: 76a914352fd6937e24d9de927b857e5d8ea9e3a8fe8f9188ac
sequence: ffffffff
tx_out_count: 01
(tx_out)
value: 0094357700000000
script_size: 19
script_pubkye: 76a91488217c4b26f9221e162fd43a919759e30069db3788ac
01000000012faad77fa20c46929a907724178a8669c0984755e6e57fe93af818870a8251d6000000001976a914352fd6937e24d9de927b857e5d8ea9e3a8fe8f9188acffffffff0100943577000000001976a91488217c4b26f9221e162fd43a919759e30069db3788ac
署名
以上のデータに4 Byteのsighash type(little endien)を付け加えて、sha256を二回適用したものに署名します。
今回はsighash allなので0x01000000
したがって署名の対称のもととなるデータは以下です。
01000000012faad77fa20c46929a907724178a8669c0984755e6e57fe93af818870a8251d6000000001976a914352fd6937e24d9de927b857e5d8ea9e3a8fe8f9188acffffffff0100943577000000001976a91488217c4b26f9221e162fd43a919759e30069db3788ac01000000
以上をpayload
としてsha256を二回。
# double hash payload
# @param payload [string] hex string
# @return [string] hex string
def double_sha256 payload
OpenSSL::Digest::SHA256.hexdigest([OpenSSL::Digest::SHA256.hexdigest([payload].pack("H*"))].pack("H*"))
end
この結果は、同じpayloadに対しては常に同じ値となり、バイト長は32 Byteです。
3dc985affd7075acd5655de9170d51076e7fb4f80535b059a5b0c5b4e19e0775
このdouble_sha256の戻り値(target)に対して対応する秘密鍵でECDSA署名を行います。
bitcoinにおける"署名"は、ECDSA署名にsighash typeの1Byte分を付け加えたものになります。
sighash_allであれば、0x01
を付加します。
# make signature
# @param data [string] hex string
# @return [Signature]
def sign data
sigobj = OpenSSL::PKey::EC.new('secp256k1')
sigobj.private_key = OpenSSL::BN.new(@priv_key, 16)
signature = sigobj.dsa_sign_asn1([data].pack("H*")).unpack("H*")
@signature = signature[0] + @sighash
self
end
署名に関してはいくつか注意点があります。
同じデータに対して署名をしても、署名値は毎回異なります。
ECDSA署名では署名時にランダムな値を利用しているためです4。
署名はDER形式でなければなりません。
DER形式についてはBIP66で定義されています。
ECDSA署名のSのサイズに制限があります。
大きすぎるとエラーで教えてくれます。事前にチェックしておきましょう。
Low S valueについてはBIP62で定義されています。
また、Sのサイズを自分で変更した際には、DER形式でのSサイズおよび署名全体のサイズ部分も変えなければいけないので注意してください。
この署名をinputのsignature script部分に入れてトランザクションの完成です。
コード全体はgithubにあるのでよければ参考に(なるのか?)してください。
もっとくわしく!
mastering bitcoin
Transaction/Bitcoin Wiki
BIP62
BIP66