P2SH宛てのトランザクションを作ろうとしたとき、スクリプトはちゃんと動いているかな?とデバッグしてみたくなります。
BitcoinのRuby実装は、スクリプトをScriptクラスで扱っていて、そのメソッドの一つにrunがあるので、それを使いながら動きを見てみましょう。
特に、スクリプト実行中のスタックの状態が見れるdebugという配列はなかなか便利です。(後述)
P2PKHのスクリプト
まずは、シンプルなP2PKHのとき。
こんなvoutがあったとする。
"vout": [
{
"value": 0.99900000,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 687eb2c06a660cdbab88e9ea6b0ce45a1fddb99e OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914687eb2c06a660cdbab88e9ea6b0ce45a1fddb99e88ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"mq3UM9q26id4iJaguoST1RaHEJAtZuZCLK"
]
}
}
]
これを、新しいトランザクションのvinに組み込むときに、署名の検証と署名をつけるわけですが、
stringで表現すると、「OP_DUP ...」。hexで表現すると「76a...」
Scriptオブジェクトを作る
script_hex = '76a914687eb2c06a660cdbab88e9ea6b0ce45a1fddb99e88ac'
script = Bitcoin::Script.new(script_hex.htb)
=> #<Bitcoin::Script:0x007ff8fb8876f0 @raw_byte_sizes=[25, 0], @previous_output_script=nil, @input_script="v\xA9\x14h~\xB2\xC0jf\f\xDB\xAB\x88\xE9\xEAk\f\xE4Z\x1F\xDD\xB9\x9E\x88\xAC", @raw="v\xA9\x14h~\xB2\xC0jf\f\xDB\xAB\x88\xE9\xEAk\f\xE4Z\x1F\xDD\xB9\x9E\x88\xAC", @chunks=[118, 169, "h~\xB2\xC0jf\f\xDB\xAB\x88\xE9\xEAk\f\xE4Z\x1F\xDD\xB9\x9E", 136, 172], @exec_stack=[], @stack_alt=[], @stack=[], @last_codeseparator_index=0, @do_exec=true>
# 想定したスクリプトが作れたか確認。jsonのasmと同じ値であればOK
script.to_string
=> "OP_DUP OP_HASH160 687eb2c06a660cdbab88e9ea6b0ce45a1fddb99e OP_EQUALVERIFY OP_CHECKSIG"
# アドレスに変換してみる
hash160_to_address('687eb2c06a660cdbab88e9ea6b0ce45a1fddb99e')
=> "mq3UM9q26id4iJaguoST1RaHEJAtZuZCLK"
実行する
実行といえば、run。
script.run
=> false
うん。
scriptPubKey単体では実行できない。scriptSigと合わせて初めて正常に動作する。(後述)
引数を渡す
引数を渡して使うらしい。do〜endは、runの中でコールバックされる。
block_timestamp = 200 # 適当
opts = {} # 適当
tx = (省略) # Scriptで検証するためのtx。(つまり、UTXOを指定するvin)
sig_valid = script.run(block_timestamp, opts) do
|pubkey,sig,hash_type,subscript|
hash = tx.signature_hash_for_input(in_idx, subscript, hash_type)
Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
end
=> false
なるほど、、、わからん
do〜end句は、validate用のためのコールバック。このコールバックを定義していないと、OP_CHECKSIGが何でもtrueを返してしまう。(末尾参考)
自力でrun
runの中身を逐次実行してみる。コールバックはその場で呼ぶ。
P2PKH
公式サイト(参考)によると、P2PKHのトランザクションにつけるスクリプトの実行(検証)には、次のようにおこなう
というわけで、vinの"scriptSig"とUTXOの"scriptPubKey"を組み合わせて、Script
を作る
# 前のTxのscriptPubKeyを取る
# 注意:もし、それがP2SHであれば、scriptPunKeyがないので、エラーになる。
script_pubkey = prev_tx.out[prev_tx_out_index].pk_script
tx.in[1].script
=> "H0E\x02!\x00\xB6=\xE7\xD8\x11;\x80\x10X\xCDL3+L{,\xA1~\x8Dr\xCC\xC7\x13\xBA\xC7\xEF\x14+\xCF\xD0}\xAA\x02 V\x92\x9Dt}\xC29\xC9\xC8]\x7F\xBC\xAF\xD4\x15\xEC\r9\x19\x81&\xC2\x9F\xAAf3q{\x04d\x1D\v\x01!\x02r\xE3=\xAB\x1Eo(\x170\xC4\xA7\xAA\xE5\xEE\x85\xE1\xCD\xE6\x10+\x06\xC6\x1E\xE7\xA3TO\xD0\xA1\x8D,\xEC"
script_pubkey.to_string
=> "v\xA9\x14h~\xB2\xC0jf\f\xDB\xAB\x88\xE9\xEAk\f\xE4Z\x1F\xDD\xB9\x9E\x88\xAC"
# scriptを作る
script = Bitcoin::Script.new(tx.in[1].script + script_pubkey)
# scriptを見る
script.to_string
=> "3045022100b63de7d8113b801058cd4c332b4c7b2ca17e8d72ccc713bac7ef142bcfd07daa022056929d747dc239c9c85d7fbcafd415ec0d39198126c29faa6633717b04641d0b01 0272e33dab1e6f281730c4a7aae5ee85e1cde6102b06c61ee7a3544fd0a18d2cec OP_DUP OP_HASH160 687eb2c06a660cdbab88e9ea6b0ce45a1fddb99e OP_EQUALVERIFY OP_CHECKSIG"
# 1: sig (encrypted payload with privkey)
# 2: pubkey (no-hash)
# -- 上 scriptSig || 下 scriptPubKey
# 3: OP_DUP
# 4: OP_HASH160 (in order to derive hash160 of pubkey in 3 which is equal to 2)
# 5: hash160 of pubkey
# 6: OP_EQUALVERIFY
# 7: OP_CHECKSIG
# 実行する
script.run
=> true
おお!
P2SH
P2PKHと同じようにやってみる。ただし、前のトランザクションにはscriptPubKeyがないので、代わりにredeem script(何らかの手段で事前に共有)を元に使うことに注意。
このとき、redeem_script(P2PKHのscriptPubKey(前のTxのvout中に記載)に相当)に合わせて、scriptSigも書き換えなければならない。便宜上、ここでは、spend_scriptと呼ぶ。
仮に、P2PKHのように、単に"scriptSig + redeem_script"でスクリプトを作成すると、プログラム実行のスタックがうまく処理されずに、falseとなる。
繰り返しになるが、redeem_scriptからspend_scriptを作り、それとscriptSigと合体させたスクリプトを実行する。
redeem_script_hex =
'0175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac'
=> "0175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac"
# redeem_scriptをScriptオブジェクトにする
redeem_script = Bitcoin::Script.new(redeem_script_hex.htb)
=> #<Bitcoin::Script:0x007ff8face0c70 @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>
# Scriptを作成
script = Bitcoin::Script.new(tx.in[0].script + spend_script)
# scriptを見てみよう
script.to_string
=> "304402200d1568e4762531c08a9b81520162c1f8d8927dc54dd792d7942d969e438c917e022063c38a7f3177e0f18b90cf5ffe011388d4bc3aa46d2e59ae53cac008a170d0f601 0175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac 75 OP_NOP2 OP_DROP 02057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011b OP_CHECKSIG"
# 1: sig (encrypted payload with privkey)
# 2: pubkey (no-hash)
# -- 上 scriptSig || 下 scriptPubKey
# 3: 75 (locktime in little endian of 117)
# 4: OP_NOP2 (operation of check locktime)
# 5: OP_DROP (drop the top of stack that is 2)
# 6: pubkey (no in hash)
# 7: OP_CHECKSIG
# 念のため、本当にキーはあっているか確認
pubkey_to_address('02057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011b')
=> "n4ZjDj9GArupgYd5fnCgHbQJYzw6W7EXsg"
pubkey_to_address('0175b1752102057820710677d967d13b6be404a2622498ce0f84cbb107b9003f83337a6d011bac')
=> "n1zMx3XjbE45vewnSTou4WzrEfXoxe19Ux"
# あれ??
うーん、検証中
デバッグ
trueとfalseにたどり着くまでの過程が見たいとき。
script = Bitcoin::Script.from_string("1112223330 123456 OP_CHECKSIG")
script.debug
=> [[], "PUSH DATA 1112223330", [["1112223330"]], "PUSH DATA 123456", [["1112223330"], ["123456"]], "OP_CHECKSIG", [1], "RESULT"]
# 初期状態
script.debug[0]
=> []
# 第一段階
script.debug[1]
=> "PUSH DATA 1112223330"
# 第二段階
script.debug[2]
=> [["1112223330"]]
# 以降、各段階ごとに配列に入っている
script.debug[4]
=> [["1112223330"], ["123456"]]
script.debug[5]
=> "OP_CHECKSIG"
script.debug[6]
=> [1] # ??!?!?!?!!?!
script.debug[7]
=> "RESULT"
true(=1)になっている原因は、OP_CHECKSIG実行時にコールされるコールバックメソッドが未定義のため。
# run the script. +check_callback+ is called for OP_CHECKSIG operations
def run(block_timestamp=Time.now.to_i, opts={}, &check_callback)
# 省略
m.call(check_callback)
OP
OP_DROP
Removes the top stack item.
スタックの最上位の項目を削除する。OPCODE = 117(0x75)
OP_CHECKSIG
OPCODE = 172 (0xAC)
引数1 = sig, 引数2 = pubkey
戻り値 True / false
The entire transaction's outputs, inputs, and script (from the most recently-executed OP_CODESEPARATOR to the end) are hashed. The signature used by OP_CHECKSIG must be a valid signature for this hash and public key. If it is, 1 is returned, 0 otherwise.
ハッシュ(トランザクションのoutput, input, scriot)と、pubkeyが妥当か検討する。
(どういうこと?)
OP_NOP2
そのうち、OP_CHECKLOCKTIMEVERIFYという名前に変わる
OPCODE = 177 (0xb1)
引数 = なし、戻り値 = なし(またはfail)
Marks transaction as invalid if the top stack item is greater than the transaction's nLockTime field, otherwise script evaluation continues as though an OP_NOP was executed. Transaction is also invalid if 1. the stack is empty; or 2. the top stack item is negative; or 3. the top stack item is greater than or equal to 500000000 while the transaction's nLockTime field is less than 500000000, or vice versa; or 4. the input's nSequence field is equal to 0xffffffff. The precise semantics are described in BIP 0065
トランザクションのnLockTimeが、スタック最上位の数よりも(greater than)大きいときに無効になる。(すなわち、nLockTimeが、スタック最上位の数(redeem_scriptで指定)以下のときに有効になる。
その他の無効条件
4. inputのnSequence fieldが0xffffffffのときも、無効になる。
参考