すみません、ウソです。
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様に感謝いたします。