10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Bitcoin::Scriptを実行する

Last updated at Posted at 2016-08-14

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のときも、無効になる。

 参考

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?