以前、絶対時間を使用するOP_CLTV(BIP-65)で指定期間まで資金をロックするスクリプトを作成したので↓
techmedia-think.hatenablog.com
今回は、相対時間を使用するOP_CSV(BIP-112)↓を使って同様のことをやってみる。
techmedia-think.hatenablog.com
OP_CLTVとOP_CSVの違い
どちらを使っても資金のロックは可能だが、以下のような違いがある。
- OP_CLTVはスクリプト内でCHECKLOCKTIMEVERIFY(OP_NOP2)の引数に与えられた時間とnLocktimeを比較し、nLocktimeを過ぎていない場合はスクリプトのインタプリタはエラーになりブロードキャストできない(nLockTimeに対してCHECKLOCKTIMEVERIFYの引数を比較することで、指定されたブロック高もしくはブロックタイムに達しているか)。
CLTVで指定するロックタイムは絶対時間(ブロック高 or タイムスタンプ)。 - OP_CSVはスクリプト内でCHECKSEQUENCEVERIFY(OP_NOP3)の引数に与えられた時間と入力のnSequenceを比較し、引数で指定されたロックタイムより前のnSequenceが設定された場合はスクリプトのインタプリタはエラーになりブロードキャストできない。
CSVで指定するのは出力がブロックに含まれてからの相対的な経過時間(ブロック数 or 512秒単位の時間)
資金を凍結する
CLTVの時と同様、一定期間資金をロックするスクリプトが↓
<expiry time> CHECKSEQUENCEVERIFY DROP <pubkey> EQUALVERIFY CHECKSIG
regtestモードで、bitcoin-rubyを使って実装したコードが↓
今回ロックタイムはブロックベースで5ブロックほどロックするように記載。
require 'bitcoin' include Bitcoin::Util Bitcoin.network = :regtest alice_key = Bitcoin::Key.from_base58('アリスの秘密鍵') lock_block = 5 # ロックするブロック数 def hold_script(alice_key, lock_block_count) Bitcoin::Script.from_string("#{lock_block_count} OP_NOP3 OP_DROP #{alice_key.pub} OP_CHECKSIG") end redeem_script = hold_script(alice_key, lock_block) p2sh = Bitcoin::Script.new(Bitcoin::Script.to_p2sh_script(hash160(redeem_script.to_payload.bth))).get_p2sh_address
を実行して生成したP2SHアドレスにBitcoinを送ると、そのUTXOは5ブロック経過するまで誰も利用できず、5ブロック経過するとアリスの秘密鍵で利用可能になる。
資金を使用する
↑でロックした資金を使用するトランザクションを作成。
# 資金をロックしたUTXOのトランザクション txid = '7fc2ef9d486582e525dcd1eb279128efba822d796a6034dbf56ebff8ccd36b53' vout = 1 def spend_script(key, unsigned_tx, redeem_script) tx = Marshal.load(Marshal.dump(unsigned_tx)) tx.in[0].script_sig = redeem_script.to_payload sig_hash = tx.signature_hash_for_input(0, redeem_script.to_payload, Bitcoin::Script::SIGHASH_TYPE[:all]) sig = key.sign(sig_hash) + [Bitcoin::Script::SIGHASH_TYPE[:all]].pack("C") Bitcoin::Script.new(Bitcoin::Script.pack_pushdata(sig) + Bitcoin::Script.pack_pushdata(redeem_script.to_payload)) end def spend_unsigned_tx(sequence, txid, vout, address) tx = Bitcoin::Protocol::Tx.new tx.ver = 2 # トランザクションのバージョンが2より小さいとBIP-68に対応していないとみなされる tx_in = Bitcoin::Protocol::TxIn.from_hex_hash(txid, vout) tx_in.sequence = sequence tx.add_in(tx_in) tx_out = Bitcoin::Protocol::TxOut.new(使用するBitcoinの量, Bitcoin::Script.new(Bitcoin::Script.to_address_script(address)).to_payload) tx.add_out(tx_out) tx end # 資金を使用するトランザクションの作成 unsigned_tx = spend_unsigned_tx([lock_block].pack("V"), txid, vout, '資金の送付先のアドレス') # ロックされたUTXOを使用するための署名を作成&トランザクションにセット alice_script = spend_script(alice_key, unsigned_tx, redeem_script) unsigned_tx.in[0].script_sig = alice_script.to_payload # 凍結された資金を利用するトランザクションのシリアライズデータ serialized_tx = unsigned_tx.to_payload.bth
指定期間後にserialized_txの内容をブロードキャストすれば、凍結されていたBitcoinが送付できる。
ちなみにUTXOのトランザクションがブロック含まれてから、まだ5ブロック経過しないうちにブロードキャストしようとするとBitcoin Coreの場合は以下のエラーが発生する。
{"code"=>-26, "message"=>"64: non-BIP68-final"}