Open Assets Protocolのアセットを1トランザクションで交換するAtomic Swapをやってみた。
シナリオ
- アリスはアセットID”oQWgTxFmK2EAYuoDvs2bLPNo9MG5nXnmyM”のアセットを1,000個持っている。
- ボブはアセットID”oM4p6N7CqTTcujSHkiRKXzzVcMuXek8aiz”のアセットを1,000個持っている。
- アリスが持つアセット300個とボブが持つアセット400個をそれぞれ交換する。
実装
仕組みは簡単でアリスとボブのそれぞれのアセットのUTXOを入力にセットし、指定した数が相手方に渡るよう出力をセットしたトランザクションを作成し、アリスとボブそれぞれが署名した後にブロードキャストする。
bitcoin-rubyとopenassets-rubyを使ってtestnetで実際にやってみたサンプルが↓
(簡易的な実装なのでUTXOの検索とかはせずとりあえず直指定)
require 'openassets' include Bitcoin::Util Bitcoin.network = :testnet3 # Open AssetsなUTXOに付与するBitcoinの量 asset_btc = 600 alice_key = Bitcoin::Key.from_base58('アリスの秘密鍵') # mmy7BEH1SUGAeSVUR22pt5hPaejo2645F1 alice_asset_id = 'oQWgTxFmK2EAYuoDvs2bLPNo9MG5nXnmyM' # アリスのアセットを保持するUTXO alice_utxo = Bitcoin::Protocol::Tx.new('010000000149c2928fed19a378e61b7327f4aa200e8de82fc47a538fb9458be922501f6ec6010000006a47304402203fd1e6a9d85112675fbbbf8bbe795467f97a04f5f473dd7b3acfa9c8d300b2460220273355e9e55ceb69625399fcf9f082fe73777876174d53d18ad8b3ade4ef948b01210292ee82d9add0512294723f2c363aee24efdeb3f258cdaf5118a4fcf5263e92c9ffffffff0358020000000000001976a91446c2fbfbecc99a63148fa076de58cf29b0bcf0b088ac00000000000000000a6a084f41010001e80700385d0100000000001976a91446c2fbfbecc99a63148fa076de58cf29b0bcf0b088ac00000000'.htb) alice_utxo_vout = 0 # アリスのBTCを保持するUTXO alice_btc_utxo = Bitcoin::Protocol::Tx.new('010000000149c2928fed19a378e61b7327f4aa200e8de82fc47a538fb9458be922501f6ec6010000006a47304402203fd1e6a9d85112675fbbbf8bbe795467f97a04f5f473dd7b3acfa9c8d300b2460220273355e9e55ceb69625399fcf9f082fe73777876174d53d18ad8b3ade4ef948b01210292ee82d9add0512294723f2c363aee24efdeb3f258cdaf5118a4fcf5263e92c9ffffffff0358020000000000001976a91446c2fbfbecc99a63148fa076de58cf29b0bcf0b088ac00000000000000000a6a084f41010001e80700385d0100000000001976a91446c2fbfbecc99a63148fa076de58cf29b0bcf0b088ac00000000'.htb) alice_btc_utxo_vout = 2 bob_key = Bitcoin::Key.from_base58('ボブの秘密鍵') # mzKzx4NheRX3PmqprDnD8UUoXjTX7RqVeL bob_asset_id = 'oM4p6N7CqTTcujSHkiRKXzzVcMuXek8aiz' # ボブのアセットのUTXO bob_utxo = Bitcoin::Protocol::Tx.new('01000000018e21aec0d7ad999c53f98de37725c0c3d3c36b24a7569051cb1c8996151a6819010000006b483045022100b4555aefc579317e9aa06f1fb1eaec01d5c4688e87ed8fc32b0ff2c3dfeba69002206556b1d8f0dd05be4435140b14cf2133f0dab662470f3a4eb6d754df4a5da4b80121035456a125cc30113748de2fff4a7bf93981876a690e1738fad91d14465be2c728ffffffff0358020000000000001976a914ce5807e32919e3e3df9806d03bd509207730e1a188ac00000000000000000a6a084f41010001e80700385d0100000000001976a914ce5807e32919e3e3df9806d03bd509207730e1a188ac00000000'.htb) bob_utxo_vout = 0 # swapするトランザクションの作成 tx = Bitcoin::Protocol::Tx.new # アリスが持つアセットのUTXOを入力にセット(600 satoshi) alice_tx_in = Bitcoin::Protocol::TxIn.from_hex_hash(alice_utxo.hash, alice_utxo_vout) alice_tx_in.script_sig = alice_utxo.outputs[alice_utxo_vout].parsed_script.to_binary tx.add_in(alice_tx_in) # ボブが持つアセットのUTXOを入力にセット(600 satoshi) bob_tx_in = Bitcoin::Protocol::TxIn.from_hex_hash(bob_utxo.hash, bob_utxo_vout) bob_tx_in.script_sig = bob_utxo.outputs[bob_utxo_vout].parsed_script.to_binary tx.add_in(bob_tx_in) # アリスが持つBTCを入力にセット(89400 satoshi) alice_btc_tx_in = Bitcoin::Protocol::TxIn.from_hex_hash(alice_btc_utxo.hash, alice_btc_utxo_vout) alice_btc_tx_in.script_sig = alice_btc_utxo.outputs[alice_btc_utxo_vout].parsed_script.to_binary tx.add_in(alice_btc_tx_in) # Marker Output # アリスが持つアセット(1000)をボブに300、おつり700を自分に # ボブが持つアセット(1000)をアリスに400、おつり600は自分に asset_quantities = [300, 700, 400, 600] marker = OpenAssets::Protocol::MarkerOutput.new(asset_quantities).build_script tx.add_out(Bitcoin::Protocol::TxOut.new(0, marker.to_payload)) # アリス→ボブ 300 tx.add_out(Bitcoin::Protocol::TxOut.new(asset_btc, Bitcoin::Script.new(Bitcoin::Script.to_address_script(bob_key.addr)).to_payload)) # アリスへのおつり700 tx.add_out(Bitcoin::Protocol::TxOut.new(asset_btc, Bitcoin::Script.new(Bitcoin::Script.to_address_script(alice_key.addr)).to_payload)) # ボブ→アリス400 tx.add_out(Bitcoin::Protocol::TxOut.new(asset_btc, Bitcoin::Script.new(Bitcoin::Script.to_address_script(alice_key.addr)).to_payload)) # ボブへのおつり600 tx.add_out(Bitcoin::Protocol::TxOut.new(asset_btc, Bitcoin::Script.new(Bitcoin::Script.to_address_script(bob_key.addr)).to_payload)) # Txの手数料を10000satoshiで計算 # Bitcoinのおつり(アリス分) 手数料=10000 satoshi alice_change = alice_btc_utxo.outputs[alice_btc_utxo_vout].value - asset_btc * 4 - 10000 tx.add_out(Bitcoin::Protocol::TxOut.new(alice_change, Bitcoin::Script.new(Bitcoin::Script.to_address_script(alice_key.addr)).to_payload)) # アリスの署名 alice_sig_hash = tx.signature_hash_for_input(0, alice_utxo, Bitcoin::Script::SIGHASH_TYPE[:none]) alice_sig = alice_key.sign(alice_sig_hash) + [Bitcoin::Script::SIGHASH_TYPE[:none]].pack("C") alice_script_sig = Bitcoin::Script.new(Bitcoin::Script.pack_pushdata(alice_sig) + Bitcoin::Script.pack_pushdata(alice_key.pub.htb)) tx.inputs[0].script_sig = alice_script_sig.to_payload alice_btc_sig_hash = tx.signature_hash_for_input(2, alice_btc_utxo, Bitcoin::Script::SIGHASH_TYPE[:none]) alice_btc_sig = alice_key.sign(alice_btc_sig_hash) + [Bitcoin::Script::SIGHASH_TYPE[:none]].pack("C") alice_btc_script_sig = Bitcoin::Script.new(Bitcoin::Script.pack_pushdata(alice_btc_sig) + Bitcoin::Script.pack_pushdata(alice_key.pub.htb)) tx.inputs[2].script_sig = alice_btc_script_sig.to_payload # アリス署名済みのトランザクションをボブに送る alice_signed_tx = tx.to_payload.bth # ボブはトランザクションを復元し、署名する tx = Bitcoin::Protocol::Tx.new(alice_signed_tx.htb) bob_sig_hash = tx.signature_hash_for_input(1, bob_utxo, Bitcoin::Script::SIGHASH_TYPE[:none]) bob_sig = bob_key.sign(bob_sig_hash) + [Bitcoin::Script::SIGHASH_TYPE[:none]].pack("C") bob_script_sig = Bitcoin::Script.new(Bitcoin::Script.pack_pushdata(bob_sig) + Bitcoin::Script.pack_pushdata(bob_key.pub.htb)) tx.inputs[1].script_sig = bob_script_sig.to_payload puts tx.to_payload.bth
アリスが署名したalice_signed_txのトランザクションデータを何らかの手段でボブに送り、ボブはそのデータからトランザクションを復元し、自分の署名を付加してブロードキャストすればOK。
もし、アリスが署名済みのトランザクションの出力の宛先をボブが改変した場合、アリスの署名はSIGHASH_ALLで署名されているので、アリスの入力の署名検証で失敗し無効なトランザクションと判定される。
最後に出力したトランザクションデータをsendrawtransactionした結果が↓
45aab69abf0ed2d856f4405b0e5e676cd9381faf07e1ec766ef7884c32142b09