LoginSignup
8
4

More than 5 years have passed since last update.

P2SHを使いこなす

Last updated at Posted at 2016-06-20

すみません、ウソです。

p2shで苦労しているので、備忘録を書いていきます。
(適宜追加していきます。)

環境

Bitcoin 0.12.1
https://github.com/bitcoin/bitcoin

P2SHとは

Bitcoinの送金方法には、アドレスの指定の方法が主に2つある。
P2PKH (Pay to PublicKey Hash): 公開鍵のハッシュ値をアドレスとして使う方法
P2SH (Pay to Script Hash): スクリプト(オプション付き送金指示)のハッシュ値をアドレスとして使う方法
スクリプトに公開鍵ハッシュの検証指示を含めることで、意図した相手方にちゃんと届けられる。
またスクリプトはハッシュのみになるため、スクリプトの本体(RedeemScriptという)は別に送る必要がある。
Redeemとは、質入れしたものを買い戻すことを言う。
スクリプトは、bitcoin独特の言語で書かれる。逆ポーランド表記。
例えば、「1+2」という命令の場合、「1 2 OP_ADD」といった形で表現される。
要は、ここに検証の機能をもたせてしまおうということ。
(ということは、ここに検証以外のスクリプトもかけるということ)
スクリプトは、ループ処理ができない「チューリング非完全」と呼ばれる簡単なプログラムしか書けない。
(もし書きたいのであれば、Ethereumや周辺システムがフォローすればよい)

Script作成

Stringから

redeem_script = Bitcoin::Script.from_string("6e OP_NOP2 OP_DROP 03b05c78e5dbde5046d0545fa788bac2303929259aa77f5eedf1d87bddbca1bb3c OP_CHECKSIG")

情報はリトルエンディアンで書くことに注意。(6eは、110の意味。03b0...は送り先の公開鍵)

hexから

> script_hex = '016eb1752103b05c78e5dbde5046d0545fa788bac2303929259aa77f5eedf1d87bddbca1bb3cac'
> bcs = Bitcoin::Script.new(script_hex.htb)
 => #<Bitcoin::Script:0x007fecaab4d428 @raw_byte_sizes=[39, 0], @previous_output_script=nil, @input_script="\x01n\xB1u!\x03\xB0\\x\xE5\xDB\xDEPF\xD0T_\xA7\x88\xBA\xC209)%\x9A\xA7\x7F^\xED\xF1\xD8{\xDD\xBC\xA1\xBB<\xAC", @raw="\x01n\xB1u!\x03\xB0\\x\xE5\xDB\xDEPF\xD0T_\xA7\x88\xBA\xC209)%\x9A\xA7\x7F^\xED\xF1\xD8{\xDD\xBC\xA1\xBB<\xAC", @chunks=["n", 177, 117, "\x03\xB0\\x\xE5\xDB\xDEPF\xD0T_\xA7\x88\xBA\xC209)%\x9A\xA7\x7F^\xED\xF1\xD8{\xDD\xBC\xA1\xBB<", 172], @exec_stack=[], @stack_alt=[], @stack=[], @last_codeseparator_index=0, @do_exec=true> 
> bcs.to_string
 => "6e OP_NOP2 OP_DROP 03b05c78e5dbde5046d0545fa788bac2303929259aa77f5eedf1d87bddbca1bb3c OP_CHECKSIG" 

OP_NOP2はCHECKLOCKTIMEVERIFYと呼ばれる命令。BIP-65で規定されています。

詳しくは、こちら
http://techmedia-think.hatenablog.com/entry/2016/05/31/184257

ちなみに、locktimeだけを取り出したい場合は、こうすればOK。ただし、常に0番目にあるわけではないので、本当はもう少しちゃんと作らないとNG。

locktime = bcs.to_string.split[0]

P2SHアドレス作成

Bitcoin::Scriptから

p2sh_address = Bitcoin::Script.new(Bitcoin::Script.to_p2sh_script(hash160(redeem_script.to_payload.bth))).get_p2sh_address

カンタンネ。

hexから

# Create Bitcoin::Script (hash160) object from script_hex
bcs_hash160 = Bitcoin::Script.new(Bitcoin::Script.to_p2sh_script(hash160(script_hex)))

# Obtain a p2sh address from the script
p2sh_addr = bcs_hash160.get_p2sh_address
# => 2NEbifo1SsiCYMQhGxGCg3tcTzR8xHuhqeH

Script変換

以下のようなScriptがあったとする。これをDBに保存したり、先方に送ったりするときには、文字列などに変換したくなる。

> redeem_script
=> #<Bitcoin::Script:0x007fecae9e3430 @raw_byte_sizes=[39, 0], @previous_output_script=nil, @input_script="\x01n\xB1u!\x02\xAA\xA0\xA7\x96\x9F\e\xED;R\x83vXbY\x89\x13\xAC\nj\xDA\x19\xC0\xD7\xF9u\rf[\xA1\xD1A\x00\xAC", @raw="\x01n\xB1u!\x02\xAA\xA0\xA7\x96\x9F\e\xED;R\x83vXbY\x89\x13\xAC\nj\xDA\x19\xC0\xD7\xF9u\rf[\xA1\xD1A\x00\xAC", @chunks=["n", 177, 117, "\x02\xAA\xA0\xA7\x96\x9F\e\xED;R\x83vXbY\x89\x13\xAC\nj\xDA\x19\xC0\xD7\xF9u\rf[\xA1\xD1A\x00", 172], @exec_stack=[], @stack_alt=[], @stack=[], @last_codeseparator_index=0, @do_exec=true> 

hex変換

redeem_script.to_payload.bth
#=> "016eb1752102aaa0a7969f1bed3b5283765862598913ac0a6ada19c0d7f9750d665ba1d14100ac" 

文字列変換

redeem_script.to_string
# => "6e OP_NOP2 OP_DROP 02aaa0a7969f1bed3b5283765862598913ac0a6ada19c0d7f9750d665ba1d14100 OP_CHECKSIG" 

ちなみに、この「02aaa...」は受取人の公開鍵ハッシュ。アドレスを見る場合は以下のようにする。

include Bitcoin::Util
pubkey_to_address('02aaa0a7969f1bed3b5283765862598913ac0a6ada19c0d7f9750d665ba1d14100')
=> "mkXBzp5CxTHkqPj9mp69VTFHbsgn557p74"

P2SHアドレス検証

Bitcoin::Utilに、methodあり。mainnet/testnetはUtil内部変数で、自動判断の様子。

module Bitcoin
  module Util
  # get type of given +address+.
  def address_type(address)
    return nil unless valid_address?(address)
    case decode_base58(address)[0...2]
    when address_version; :hash160
    when p2sh_version;    :p2sh
    end
  end

秘密鍵を抽出

署名に使うpubkeyなどを作るために、秘密鍵があると何かと便利。そんな時は、bitcoin-cliを使うと楽。

% bitcoin-cli dumpprivkey mxTDdP1uAhfK32zFt9SdV1UnftMoDqMbq6

mxTDdP1uAhfK32zFt9SdV1UnftMoDqMbq6は、bitcoinのアドレス。ローカル端末内に対応する秘密鍵があると、表示してくれる。もし、端末内になければ、エラーメッセージが帰ってくる。

これを使って、Bitcoin::Keyオブジェクトを作ると、いろいろ便利。

Bitcoin.network = :testnet3    # <= これ重要。ないと、[RuntimeError: Invalid version]が表示される
key = Bitcoin::Key.from_base58('秘密鍵')

key.pubで、公開鍵になる。楽チン。

考察・注意展

P2SHアドレスを生成するためには、そのUTXOの利用を相手方に制限するために、相手方のpubkeyが欲しいところ。pubkeyを使わない場合は、ほかの手段による検証のロジックをスクリプトに書く必要あり。
(だれでも使えるUTXOを作る場合は、この限りでない)

privkeyを共有するよりもマシなので、何らかの方法で公開鍵を共有する。


P2SHの学習にあたり、良質な記事を公開されているハウ・インターナショナルのAzuchi様に感謝いたします。

8
4
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
8
4