Bitcoinトランザクションをバイナリから理解していく

お知らせ
Table of Contents

この記事はBlockchain Advent Calendar 2019の1日目の記事です。

対象読者

  • アドレスとかトランザクションなどの基本的な用語はしってるけどよく理解できていない方
  • Walletを作ってみたい方

動機

なぜか最近、自分の周りでブロックチェーン関連の仕事をする人が増えており、質問を受ける機会が多い。
ブロックチェーンの基本的な用語や情報などはWeb上に多く見られるが、仕様に関して日本語でまとまってわかりやすく説明されている記事などはまだまだ少ないように思う。
ブロックチェーンを理解するためには、大枠として、「トランザクション」と「ブロック」の仕様をそれぞれで理解すると進めやすい。
これら2つは、Mastering Bitcoinの目次で言えば、下記に該当する。

  • トランザクション => 「キー、アドレス、ウォレット」「トランザクション」
  • ブロック => 「Bitcoinネットワーク」「ブロックチェーン」「マイニングとコンセンサス」

そこで、本記事では、「トランザクション」の部分に注目して、前準備なしに触りながら理解できる記事を書ければと思っている。
なお、対象通貨はBitcoinとする。

流れ

  1. トランザクションのシリアライゼーションフォーマットを理解する
  2. Scriptは何を表現しているのかを理解する

実際にmainnetやtestnet上に流れているトランザクションを見ながら理解を深めていく。

トランザクションのシリアライゼーションフォーマットを理解する

トランザクションの種類

現在、Bitcoinのブロックチェーン上では下記3種類のトランザクションが存在している。

  1. 普通のトランザクション(非coinbase, 非segwit)
  2. segwitトランザクション
  3. coinbaseトランザクション

今後のBitcoinのアップデートなどにより、これら以外のトランザクションが出てくる可能性もある。
なお、普通のトランザクションに関して、「普通の」とつけたのは、segwit及びcoinbaseトランザクションと区別するためで、通常は非segwitトランザクションと呼ばれることが多い。
基本ということで、今回はこの内の「1. 普通のトランザクション(非coinbase, 非segwit)」を取り上げる。

普通のトランザクションを解析してみる

ブロックチェーン上にすでに存在するトランザクションを見て、理解を深めていく。
トランザクションを解析するためには、トランザクションの実体である、生トランザクション(RawTransaction)を取得していく。
生トランザクションを取得するためには、bitcoin-cli getrawtransaction を利用すれば良い。
そのため、普通のトランザクションのTxID1を取得する必要がある。

普通のトランザクションのTxHashの取得

普通のトランザクションかsegwitトランザクションかを見分けるには、ChainFlyerがお手軽だ。
ChainFlyer

サイトにアクセスしたら、画面上部から落ちてくる正六面体で、下記画像の一番右のような、プレーンな正六面体を選ぶ。

そうすると、「トランザクション」の文字の下にTxIDが表示される。

ちなみに鍵があるものや、小さな正六面体があるものを選ぶと、下記のように「トランザクション」の文字の横にsegwitと表示される。こちらがsegwitトランザクションとなる。

生トランザクションの取得

それでは、画像で使われている5a4216acfb7b8619b4455925e77d789867cea06b74c77f09be41037fcddc062cTxIDを使って、生トランザクションを取得する。
読者の方で、bitcoin-cliが使える環境を用意されている方は稀だと思うので、下記サイトを利用する。
ChainQuery

このサイトでは、bitcoin-cliの各機能がブラウザ上から使えるようになっている。2

bitcoin-cli getrawtransaction が使いたいので、getrawtransactionのページに遷移する。
https://chainquery.com/bitcoin-cli/getrawtransaction

Transaction IDの部分に5a4216acfb7b8619b4455925e77d789867cea06b74c77f09be41037fcddc062cをコピペして、Execute Commandを実行する。
(reCAPTHCAが表示されていたら、チェックを入れてください。)

そうすると、結果のJSONがその下のCommand result: bitcoin-cli getrawtransactionと書かれている箇所に表示される。

ここでresultに表示されている

01000000015975479e8161302eca4b69ad687418655dfc6d960da3c642542d712193bc6127010000006b48304502210094ea6b6cce6b1224047c39fd5da6ce932d0a5091a5d20f4e1dd4d2e1a6c04733022065d606a3e210650c1ad857d99a1eadd12ab7f08d5def1913766117124be0cec7012102a166ea841c7344b2d3a9a1f9e890d8b2144b5b975cb0ed87d68f12065df89ea7ffffffff0258c90900000000001976a9141eca25f20d936a6e176701b6fe2953d358d9add788ac86022b00000000001976a9144edc3518c2b5eb0a35535d0c47f164b7b5423a0a88ac00000000

が生トランザクションとなる。

生トランザクションをシリアライゼーションフォーマットに当て込む

これだけ見ていると頭が痛くなる方もいるかもしれないが、形に当てはめてしまえば割と簡単。
トランザクションはバイナリのフォーマット(シリアライゼーションフォーマット)が仕様で定められているので、そこに当て込むだけである。
普通のトランザクションのシリアライゼーションフォーマットはChaintopeの安土さんの記事がBitcoin公式リファレンスの翻訳になっていてわかりやすい。
トランザクションのシリアライゼーションフォーマット仕様

上記記事では、シリアライゼーションフォーマットの入れ子関係が分かりづらいので、下記表にまとめてみた。(各詳細は上記記事で補足してください。)

名前 サイズ(byte) 内容
version 4 バージョン番号
tx_in count 1,3,5,9いずれか TxInputの数
tx_in prev_out hash 32 消費するUTXOが含まれるTxID
index 4 上記TxIDに該当するTxのOutputIndex
script bytes 1,3,5,9いずれか あとに続くScriptSigのサイズ
signature script script bytes分 ScriptSig
sequence 4 シーケンス番号
tx_out count 1,3,5,9のいずれか TxOutputの数
tx_out value 8 送付するsatoshiの数量
pk_script bytes 1,3,5,9いずれか あとに続くScriptPubkeyのサイズ
pk_script pk_script bytes分 ScriptPubkey
lock_time 4 LockTime

※tx_inとtx_outはそれぞれのcount数分連続して続きます。

サイズの項目の「1,3,5,9のいずれか」は、対象のデータサイズをもとに、下記ルールによって決まる。

表現する値 バイト数
0以上252以下 1
253以上0xffff以下 3
0x10000以上0xffffffff以下 5
0x100000000以上0xffffffffffffffff 9

実際にこの表の形に、bitcoin-cli getrawtransaction で得られた結果を先頭から当てはめていってみると、下記のようになる。

名前 サイズ(byte) バイナリ(Little endian) 意味
version 4 01000000 バージョン1
tx_in count 1 01 tx_inが1個
tx_in_1 prev_out hash 32 5975479e8161302eca4b69ad687418655dfc6d960da3c642542d712193bc6127 TxID:2761bc9321712d5442c6a30d966dfc5d65187468ad694bca2e3061819e477559の1つ目のUTXOを消費
index 4 01000000
script bytes 1 6b ScriptSigは107byteで左記の内容
signature script script bytes分 48304502210094ea6b6cce6b1224047c39fd5da6ce932d0a5091a5d20f4e1dd4
d2e1a6c04733022065d606a3e210650c1ad857d99a1eadd12ab7f08d5def1913
766117124be0cec7012102a166ea841c7344b2d3a9a1f9e890d8b2144b5b975c
b0ed87d68f12065df89ea7
sequence 4 ffffffff デフォルト値
tx_out count 1 02 tx_outが2個
tx_out_1 value 8 58c9090000000000 19byteのScriptPubkey: 76a9141eca25f20d936a6e176701b6fe2953d358d9add788acで表現されるアドレスへ0.00641368BTC送付
pk_script bytes 1 19
pk_script pk_script bytes分 76a9141eca25f20d936a6e176701b6fe2953d358d9add788ac
tx_out_2 value 8 86022b0000000000 19byteのScriptPubkey: 76a9144edc3518c2b5eb0a35535d0c47f164b7b5423a0a88acで表現されるアドレスへ0.02818694BTC送付
pk_script bytes 1 19
pk_script pk_script bytes分 76a9144edc3518c2b5eb0a35535d0c47f164b7b5423a0a88ac
lock_time 4 00000000 locktimeは不使用

値の表現がLittle endianになっていることに気をつけよう。

segwitトランザクションでもcoinbaseトランザクションでも、それぞれのシリアライゼーションフォーマットが用意されており、同様に当て込んでいくことができる。
トランザクションのシリアライゼーションフォーマットへの当て込み方はここまでで理解できたと思うので、次はトランザクションの内容を理解するために、Scriptを理解していこう。
なお、locktimeやtx_in-sequenceに関しては、本稿では説明しないが、簡単にはトランザクションのブロックへの取り込みタイミングを制御するために使われる。詳しくはBIP-68BIP-125などを参考にして欲しい。

Scriptを理解する

シリアライゼーションフォーマットの中を見ていくと、signature script(以下ScriptSigと呼ぶ)やpk_script(以下ScriptPubkeyと呼ぶ)が謎の部分だろう。
これらは総称してScriptと呼ばれるが、ここがBitcoinトランザクションの肝になってくる。

Scriptの説明に、よくP2PKHやP2SHなどの言葉が出てくると思うが、そもそもそれ以前にScriptの役割を知っておいたほうがよいと考えている。
BitcoinにおけるScriptとは、間違いを恐れずに一言で表現すると「UTXOの消費にかかわるクイズを定義するもの」である。
クイズのお題がtx_outに含まれるScriptPubkeyであり、それに対する回答がtx_inに含まれるScriptSigである。
UTXO対象のScriptPubkeyに対して、正解のScriptSigを含むトランザクションをブロックチェーン上に送信したときに、クイズに正解したということで、BTC残高の移動が行われる。
逆に言うと、クイズが解かれるまでは、残高はそのクイズによってLockされているわけである。
これがScriptSig領域に存在するScriptがunlocking scriptと呼ばれ、ScriptPubkey領域に存在するScriptがlocking scriptと呼ばれる所以である。3

また、クイズによってBTC残高がLockされる == BTC残高が移動する、ということは、アドレスからアドレスへBTCが移動しているということである。
後述するが、アドレスはScriptPubkeyから導出できるので、残高がLockされる == 残高が移動する、という表現が成り立つ。

それでは先程のトランザクションのScriptSig48304502210094ea6b6cce6b1224047c39fd5da6ce932d0a5091a5d20f4e1dd4d2e1a6c04733022065d606a3e210650c1ad857d99a1eadd12ab7f08d5def1913766117124be0cec7012102a166ea841c7344b2d3a9a1f9e890d8b2144b5b975cb0ed87d68f12065df89ea7は、どのScriptPubkey(クイズ)に対する回答か?というと、一つ前のトランザクションのScriptPubkey(クイズ)に対する回答になる。
自分が勉強を始めたときによく勘違いしていたのが、ScriptPubkey(クイズ)と対応するScriptSig(回答)が同一のトランザクションに含まれていると勘違いしていた。
同一のトランザクション内に存在するScriptSigとScriptPubkeyは、同じトランザクションに含まれているというだけで、全く関係性はない。

ここまでの話をイメージしやすいように下記の図を用意したので、説明と合わせて理解できるまでじっくり見て欲しい。P2PKHやP2SHなどのスクリプトの種類を知る前にこの関連性を知っておくことが大事である。

この前提を知った上で、先程のトランザクションのScriptを具体的に見ていく。

Scriptの解析

まず、ScriptSigから見ていこう。

ScriptSigはクイズのお題に対する回答なので、まずお題を知る必要がある。
お題は、tx_inのprev_outから知ることが可能だ。ここに、一つ前のTxIDとTxOutputのindex値が格納されている。

PrevOutTxID: 2761bc9321712d5442c6a30d966dfc5d65187468ad694bca2e3061819e477559
PrevOutのTxOutputIndex: 1

これより、お題であるScriptPubkeyを前述のgetrawtransaction で知ることが可能だ。4
またChainQueryにお世話になろう。
https://chainquery.com/bitcoin-cli/getrawtransaction

そうすると、以下の結果が得られる。

0200000000010184bde6688f4c23b10dc667f785af08290ac2f0d11958213c8afcfd3c087cb2f90600000000fdffffff0abef40100000000001976a91475d286c168187202b18f2e236c340af9f1cae4bd88ac84ce3400000000001976a914742e1f933af72f03876eeadac8402cfcafd4533288ac4b0a0d00000000001976a91477b97182bc281a8fbe1b0c39ca8c9e6fd2b13ce488acaf250800000000001976a9145b1dbf9b075e156f53abeb7cb9176fe54befa41b88ac75ae0600000000001976a914f510e76146715f0b8b9f3fd342efcac33edd5e8e88ac104b1000000000001976a91405724f9ff55ffb3ed13f1c951251a96bcd4b56c388ac1fe73000000000001976a91422e068ba8c926017436d1c6e5b66754c5da3a43688acf1918f01000000001600145f7ddd38fa71a27af7e847a4dd194a6048eae663ce8f1300000000001976a91486f292f7ad4df5aecbffd516dca55b91c1ec973d88ac8d0c3900000000001976a91476db8fa6f32d531f8a0c1c952fa5063e77f4564888ac02473044022064274d9e12d23dbe718215973f6c8744c5f56624cd400f6a9f9828d2a78bd590022073b06769a4630fb060430c5a1c5536fd5ec68f70c0687b73ef0552ca7bdfe0e00121024362d42ce1d8a639afdd14b3fb281a79ed1a5761e3537519d986eddee5673582a5390900

この生トランザクションをシリアライゼーションフォーマットに当て込めてもいいが、いちいちめんどくさいし、このトランザクションはsegwitトランザクションで、segwitトランザクションのシリアライゼーションフォーマットは説明していないので、今回はツールを使わせてもらおう。

bitcoin-cli環境がある方は、decoderawtransaction を実行すると、上記トランザクションがシリアライゼーションフォーマットに合わせてJSON出力として得られる。
無い方は、BlockCypherのページに、生トランザクションをデコードしてくれるページがあるので、こちらを使用する。
https://live.blockcypher.com/btc/decodetx/

下記画像のように、上記生トランザクションを貼り付けて、Decode Transactionのボタンを押すと、JSONでデコード結果が表示される。5

今回ほしいものは、このトランザクションのTxOutputのindex 1番目なので、下記結果のみを利用する。(indexは0からはじまるので注意。)

...
    "outputs": [
        ...
        {
            "addresses": [
                "1BbJdxaNRy4rUePTE9hcQ9rTgAK67SWMxx"
            ], 
            "script": "76a914742e1f933af72f03876eeadac8402cfcafd4533288ac", 
            "script_type": "pay-to-pubkey-hash", 
            "value": 3460740
        }, 
...

この"script": "76a914742e1f933af72f03876eeadac8402cfcafd4533288ac"がScriptPubkey(お題)である。
このままだと、このお題が何を表現しているかわからないので、このscriptを解析する。

Scriptの解析にはbitcoin-cliのdecodescript を使用する。
bitcoin-cli環境が無い方は、下記サイトを利用させていただこう。
Bitcoin IDE

画像のように、Assemblyと書かれているタブを選択し、上記scriptを貼り付け、Scriptと書かれているタブに戻ると、解析結果が表示される。

OP_DUP OP_HASH160 742e1f933af72f03876eeadac8402cfcafd45332 OP_EQUALVERIFY OP_CHECKSIG

実はこれはP2PKH(Pay to Public Key Hash)のScriptである。

これに対して、ScriptSigも同様に解析すると、下記のようになる。

304502210094ea6b6cce6b1224047c39fd5da6ce932d0a5091a5d20f4e1dd4d2e1a6c04733022065d606a3e210650c1ad857d99a1eadd12ab7f08d5def1913766117124be0cec701 02a166ea841c7344b2d3a9a1f9e890d8b2144b5b975cb0ed87d68f12065df89ea7

これは`の並びとなっており、P2PKHに対する回答となる。 これを下記のようにの順に並べ、Scriptを実行すると、1(true)`が得られる。

304502210094ea6b6cce6b1224047c39fd5da6ce932d0a5091a5d20f4e1dd4d2e1a6c04733022065d606a3e210650c1ad857d99a1eadd12ab7f08d5def1913766117124be0cec701 02a166ea841c7344b2d3a9a1f9e890d8b2144b5b975cb0ed87d68f12065df89ea7 OP_DUP OP_HASH160 742e1f933af72f03876eeadac8402cfcafd45332 OP_EQUALVERIFY OP_CHECKSIG

ここで、このScriptが正しいかどうかの検証を行いたかったが、どうもうまく動作するサイトが見つからなかったので、検証に関しては一旦置いておくとする。6

ここでは、Scriptが1(true)になれば、回答(ScriptSig)が正解であり、正解のときにBTC残高が移動する、ということがわかれば良い。

Scriptの詳細は色んな所で記事になっているので、本稿では説明しない。
最近見た記事では下記記事がわかりやすかった。上記のScriptの例と照らし合わせて見ると、理解が進むかもしれない。
Script入門

ScriptPubkeyとアドレスの関係性

前項で、正解のときにBTC残高が移動する、と書いたが、ご承知の通り、BTC残高はアドレスに紐づくものだ。
実は、ScriptPubkeyの中にアドレスの情報となるものが含まれており、ここのをもとにアドレスが導出されている。

前項で解析した一つ前のトランザクションのScriptPubkeyを見てみる。

OP_DUP OP_HASH160 742e1f933af72f03876eeadac8402cfcafd45332 OP_EQUALVERIFY OP_CHECKSIG

このScriptはP2PKHのScriptだと説明したが、Scriptもトランザクション同様仕様が定められている。
ここで742e1f933af72f03876eeadac8402cfcafd45332の部分は、公開鍵のHash1607を計算したものとなる。
アドレスは公開鍵から導出できる。公開鍵->Hash160化->Base58Check8というプロセスを経てアドレスとなる。
実際に上記Hash160でハッシュ化された公開鍵から、アドレスが導出できるか見てみよう。

下記サイトを使わせていただく。
https://bc-2.jp/tools/txeditor2.html

Etcタブに移動し、下記画像のように、先頭に00のprefixをつけ9Encode (Hex > BASE58Check)を選択してみよう。

1BbJdxaNRy4rUePTE9hcQ9rTgAK67SWMxxが結果として得られるはずだ。

このアドレスを上記で紹介したChainFlyerなどのBlock Explorerで検索してみる。
そうすると、下記画像のような結果が得られる。

Fromに一つ前のトランザクションである2761bc9321712d5442c6a30d966dfc5d65187468ad694bca2e3061819e477559が。
Toに対象トランザクションである5a4216acfb7b8619b4455925e77d789867cea06b74c77f09be41037fcddc062があることがわかる。

同様にして、5a4216acfb7b8619b4455925e77d789867cea06b74c77f09be41037fcddc062の2つのTxOutputのScriptPubkeyをBitcoinIDEで解析し、ScriptPubkeyに含まれているHash160でハッシュ化された公開鍵からBase58Checkしてアドレスを導出してみよう。
そうすると、13ooSG4xiHh4DUQUiGk7UAh8oJJ6ZAKStG18ByWF2NTxMoNDzVCt97KABQquLixNBo9Jが得られるはずだ。

このように、ScriptPubkeyからアドレスが導出できるため、TxOutputでは明示的にアドレスを指定する必要がないわけである。
そして、Scriptが正解であれば、一つ前のScriptPubkeyでLockされてた残高が新しいScriptPubkeyでLockされる == アドレスからアドレスへBTC残高の移動が行われる、というわけである。

まとめ

以上、トランザクションのシリアライゼーションフォーマットから、Scriptの解析、アドレス導出というところまで見てきた。
生トランザクションからトランザクションを理解していく記事はあんまりないんじゃないかなと思う。
実際自分がトランザクション周りを理解していく過程の中で、生トランザクションを触ったことは、理解を大きく進めることに非常に役に立った。
今回取り上げた以外のトランザクションの種類やスクリプトの種類もあるが、トランザクションの基本的な考え方はここに書いてあるものがベースとなっている。
トランザクションやスクリプト、アドレスなどの関連性がいまいちぼんやりしている方には、ぜひ読んでいただきたい。


  1. TxHashとも呼ばれる。 

  2. sign系やsend系の一部機能はおそらくセキュリティの観点から封印されている。 

  3. ScriptSig == unlocking script, ScriptPubkey == locking scriptという理解は厳密には違うが、ここでは簡単にするため、その意味で進める。詳しくはMastering Bitcoinの標準的なトランザクションの項を参照してほしい。 

  4. ChainFlayerなどのBlockExplorerからPrevTx(親Tx)をたどることも可能。今回はあえてバイナリ形式のScriptがほしいため、この方法をとっている。 

  5. このツールはdecoderawtransactionの結果にBlockCypher独自の付加情報も合わせて表示されるが、本質的に違いはない。 

  6. 上記BitcoinIDEでScriptを検証できるかと思ったが、OP_HASH160にバグが有り正しいHASH160の値が得られないのと、OP_CHECKSIGを動かすためには署名の元になったトランザクションのデータが必要でそれ入力する箇所がなく、検証できない 

  7. Hash160はSha256->RIPEMD160を行うもの 

  8. わかりやすそうなサイト->http://landau.jp/blog/275/ 

  9. prefixの種類もBase58checkする対象によって定まっている。00はP2PKHアドレスのprefixである。https://en.bitcoin.it/wiki/List_of_address_prefixes 

辻野晃一

ゲームプログラマ->TV開発->暗号通貨ウォレット開発

ブロックチェーンは全く門外漢だったが、たまたま暗号通貨ウォレット開発案件に参画。
ドキュメントが揃ってるXRPが割と好き。

フレセッツ技術ブログをフォローする
お知らせ
フレセッツ技術ブログをフォローする
HashPort技術ブログ
タイトルとURLをコピーしました