Develop with pleasure!

福岡でCloudとかBlockchainとか。

Open Assets Protocolを使ったアセットのAtomic Swap

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