この記事はBlockchain Advent Calendar 2019の1日目の記事です。
対象読者
- アドレスとかトランザクションなどの基本的な用語はしってるけどよく理解できていない方
- Walletを作ってみたい方
動機
なぜか最近、自分の周りでブロックチェーン関連の仕事をする人が増えており、質問を受ける機会が多い。
ブロックチェーンの基本的な用語や情報などはWeb上に多く見られるが、仕様に関して日本語でまとまってわかりやすく説明されている記事などはまだまだ少ないように思う。
ブロックチェーンを理解するためには、大枠として、「トランザクション」と「ブロック」の仕様をそれぞれで理解すると進めやすい。
これら2つは、Mastering Bitcoinの目次で言えば、下記に該当する。
- トランザクション => 「キー、アドレス、ウォレット」「トランザクション」
- ブロック => 「Bitcoinネットワーク」「ブロックチェーン」「マイニングとコンセンサス」
そこで、本記事では、「トランザクション」の部分に注目して、前準備なしに触りながら理解できる記事を書ければと思っている。
なお、対象通貨はBitcoinとする。
流れ
- トランザクションのシリアライゼーションフォーマットを理解する
- Scriptは何を表現しているのかを理解する
実際にmainnetやtestnet上に流れているトランザクションを見ながら理解を深めていく。
トランザクションのシリアライゼーションフォーマットを理解する
トランザクションの種類
現在、Bitcoinのブロックチェーン上では下記3種類のトランザクションが存在している。
- 普通のトランザクション(非coinbase, 非segwit)
- segwitトランザクション
- coinbaseトランザクション
今後のBitcoinのアップデートなどにより、これら以外のトランザクションが出てくる可能性もある。
なお、普通のトランザクションに関して、「普通の」とつけたのは、segwit及びcoinbaseトランザクションと区別するためで、通常は非segwitトランザクションと呼ばれることが多い。
基本ということで、今回はこの内の「1. 普通のトランザクション(非coinbase, 非segwit)」を取り上げる。
普通のトランザクションを解析してみる
ブロックチェーン上にすでに存在するトランザクションを見て、理解を深めていく。
トランザクションを解析するためには、トランザクションの実体である、生トランザクション(RawTransaction)を取得していく。
生トランザクションを取得するためには、bitcoin-cli getrawtransaction
を利用すれば良い。
そのため、普通のトランザクションのTxID
1を取得する必要がある。
普通のトランザクションのTxHashの取得
普通のトランザクションかsegwitトランザクションかを見分けるには、ChainFlyerがお手軽だ。
ChainFlyer
サイトにアクセスしたら、画面上部から落ちてくる正六面体で、下記画像の一番右のような、プレーンな正六面体を選ぶ。
そうすると、「トランザクション」の文字の下にTxID
が表示される。
ちなみに鍵があるものや、小さな正六面体があるものを選ぶと、下記のように「トランザクション」の文字の横にsegwitと表示される。こちらがsegwitトランザクションとなる。
生トランザクションの取得
それでは、画像で使われている5a4216acfb7b8619b4455925e77d789867cea06b74c77f09be41037fcddc062c
のTxID
を使って、生トランザクションを取得する。
読者の方で、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-68やBIP-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をつけ9、Encode (Hex > BASE58Check)
を選択してみよう。
1BbJdxaNRy4rUePTE9hcQ9rTgAK67SWMxx
が結果として得られるはずだ。
このアドレスを上記で紹介したChainFlyerなどのBlock Explorerで検索してみる。
そうすると、下記画像のような結果が得られる。
Fromに一つ前のトランザクションである2761bc9321712d5442c6a30d966dfc5d65187468ad694bca2e3061819e477559
が。
Toに対象トランザクションである5a4216acfb7b8619b4455925e77d789867cea06b74c77f09be41037fcddc062
があることがわかる。
同様にして、5a4216acfb7b8619b4455925e77d789867cea06b74c77f09be41037fcddc062
の2つのTxOutputのScriptPubkeyをBitcoinIDEで解析し、ScriptPubkeyに含まれているHash160でハッシュ化された公開鍵からBase58Checkしてアドレスを導出してみよう。
そうすると、13ooSG4xiHh4DUQUiGk7UAh8oJJ6ZAKStG
と18ByWF2NTxMoNDzVCt97KABQquLixNBo9J
が得られるはずだ。
このように、ScriptPubkeyからアドレスが導出できるため、TxOutputでは明示的にアドレスを指定する必要がないわけである。
そして、Scriptが正解であれば、一つ前のScriptPubkeyでLockされてた残高が新しいScriptPubkeyでLockされる == アドレスからアドレスへBTC残高の移動が行われる、というわけである。
まとめ
以上、トランザクションのシリアライゼーションフォーマットから、Scriptの解析、アドレス導出というところまで見てきた。
生トランザクションからトランザクションを理解していく記事はあんまりないんじゃないかなと思う。
実際自分がトランザクション周りを理解していく過程の中で、生トランザクションを触ったことは、理解を大きく進めることに非常に役に立った。
今回取り上げた以外のトランザクションの種類やスクリプトの種類もあるが、トランザクションの基本的な考え方はここに書いてあるものがベースとなっている。
トランザクションやスクリプト、アドレスなどの関連性がいまいちぼんやりしている方には、ぜひ読んでいただきたい。
-
TxHash
とも呼ばれる。 ↩ -
sign系やsend系の一部機能はおそらくセキュリティの観点から封印されている。 ↩
-
ScriptSig == unlocking script, ScriptPubkey == locking scriptという理解は厳密には違うが、ここでは簡単にするため、その意味で進める。詳しくは
Mastering Bitcoinの標準的なトランザクション
の項を参照してほしい。 ↩ -
ChainFlayerなどのBlockExplorerからPrevTx(親Tx)をたどることも可能。今回はあえてバイナリ形式のScriptがほしいため、この方法をとっている。 ↩
-
このツールは
decoderawtransaction
の結果にBlockCypher独自の付加情報も合わせて表示されるが、本質的に違いはない。 ↩ -
上記
BitcoinIDE
でScriptを検証できるかと思ったが、OP_HASH160にバグが有り正しいHASH160の値が得られないのと、OP_CHECKSIGを動かすためには署名の元になったトランザクションのデータが必要でそれ入力する箇所がなく、検証できない ↩ -
Hash160はSha256->RIPEMD160を行うもの ↩
-
わかりやすそうなサイト->http://landau.jp/blog/275/ ↩
-
prefixの種類もBase58checkする対象によって定まっている。
00
はP2PKHアドレスのprefixである。https://en.bitcoin.it/wiki/List_of_address_prefixes ↩