Open Assets Protocolではアセットのメタデータを定義したAsset DefinitionをURLで指定する。
techmedia-think.hatenablog.com
↑に記載されているように、Asset Definition URL
とブロックチェーン上のアセットを紐付ける方法は以下の2種類がある。
- アセット発行時の
Marker Output
のmetadetaフィールドで設定する - P2SHを使ってアセットを発行し、そのP2SHのredeem script内に設定する
openassets-rubyは前者の方法でAsset DefinitionのURL
を設定・解釈するように実装している。このMarker Output
に付加する方法は、アセットの発行をウォレットがデフォルトでサポートしているP2PKHベースで行え、設定も用意にできるといったメリットがある。しかし反面、アセットを追加発行する際に別のAsset Definition URL
を設定することも可能で、Asset ID
は同じでも参照しているAsset Definition
が異なるといったデメリットもある。
P2SHを使う方法では、P2SHであるためそのUTXOを使うにはredeem scriptを知っている必要があり、P2PKHなどに比べると手間がかかるというデメリットはあるが、Asset Definition URL
がP2SHのredeem scriptの一部になっているため、Asset Definition URL
を変えてアセットを発行するとAsset ID
が変わることになり、Asset Definition URL
の一意性を担保できる。それ以外にも、P2SHのredeem scriptのサイズの上限は520バイトであるため、Marker Output
の上限80バイトに比べて、より多くのデータを格納することができる。
Asset Definition URLを含むP2SHの作成
まず以下のようにしてAsset Definition URL
を含むP2SHのスクリプトを作成する。
require 'openassets' # 最初の引数がAsset Definition URLで、2番目の引数がこのP2SHを使用する際に必要な署名を持つOpen Asset Address。 p2sh = OpenAssets::Protocol::AssetDefinitionLoader.create_pointer_p2sh('https://goo.gl/bmVEuw', 'bX3yaoWQ5KTVmucut1jx7LapV6gy5V3sjjL')
この時内部で以下のredeem scriptを作ってる。
PUSHDATA <最初の引数で指定したURL> OP_DROP OP_DUP OP_HASH160 <2つ目のアドレスの公開鍵のハッシュ> OP_EQUALVERIFY OP_CHECKSIG
Open Assets Protocolで定義されているのはPUSHDATA
からOP_DROP
までなので、それ以降は任意のopcodeを使って自由にスクリプトを作れる。今回は通常のP2PKHのスクリプトを付与している。
あとはP2SHの仕様に従って↓P2SHスクリプトを作っている。
OP_HASH160 <redeem scriptのハッシュ> OP_EQUAL
P2SHまで作れたら
p2sh.get_p2sh_address
でP2SHのアドレスが分かるのでそのアドレス宛にBitcoinを送っておく。
続いて、いよいよアセットの発行を行う。先ほどのP2SHアドレスに送ったBitcoinのUTXOを入力にセットしたアセット発行用のトランザクションを作成し、上記のredeem scriptを満たす署名を付与する↓
from_tx = Bitcoin::Protocol::Tx.new(api.provider.get_transaction('b7e225d9988796f26c2fdd672795b2d27c667d298cf1db169cdf11ab8b064844').htb) tx = Bitcoin::Protocol::Tx.new tx.add_in(Bitcoin::Protocol::TxIn.from_hex_hash(from_tx.hash, 1)) tx.add_out(Bitcoin::Protocol::TxOut.value_to_address(600, 'mvZM34fU6wdDqF3gKd2tYA67vjWvwBHbDU')) script = OpenAssets::Protocol::MarkerOutput.new([99999]).build_script tx.add_out(Bitcoin::Protocol::TxOut.new(0, script.to_payload)) tx.add_out(Bitcoin::Protocol::TxOut.value_to_address(39400, 'mvZM34fU6wdDqF3gKd2tYA67vjWvwBHbDU')) from_key = Bitcoin::Key.from_base58('bX3yaoWQ5KTVmucut1jx7LapV6gy5V3sjjL=(Bitcoinアドレス:mt1hZLajqyc63NkWy7qvgiuum5nuTBdVZ6)の秘密鍵') redeem_script = OpenAssets::Protocol::AssetDefinitionLoader.create_pointer_redeem_script('https://goo.gl/bmVEuw', 'bX3yaoWQ5KTVmucut1jx7LapV6gy5V3sjjL') sig_hash = tx.signature_hash_for_input(0, redeem_script.to_payload) sig = Bitcoin::Script.to_pubkey_script_sig(from_key.sign(sig_hash), from_key.pub.htb) script_sig = Bitcoin::Script.new(sig + Bitcoin::Script.pack_pushdata(redeem_script.to_payload)) tx.in[0].script_sig = script_sig.to_payload puts tx.to_payload.bth
作成したトランザクションをブロードキャストする。実際にtestnetにブロードキャストしたトランザクションが↓
a98b000f28c9e8251925a1225832edf1c2992e103445b6b2cec3f5c0a81469e5
{ "hex": "01000000014448068bab11df9c16dbf18c297d667cd2b2952767dd2f6cf2968798d925e2b7010000009d47304402202254f7da7c3fe2bf2a4dd2c3e255aa3ad61415550f648b564aea335f8fcd3d92022062eab5c01a5e33eb726f976ebd3b35d3991f8a45da56d64e1cd3fd5178f8c9a6012102effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa3217753d68747470733a2f2f676f6f2e676c2f626d564575777576a9148911455a265235b2d356a1324af000d4dae0326288acffffffff0358020000000000001976a914a4fdb3ce954a3cbb49fdfc9df1712bf72749c9e788ac00000000000000000b6a094f410100019f8d0600e8990000000000001976a914a4fdb3ce954a3cbb49fdfc9df1712bf72749c9e788ac00000000", "txid": "a98b000f28c9e8251925a1225832edf1c2992e103445b6b2cec3f5c0a81469e5", "hash": "a98b000f28c9e8251925a1225832edf1c2992e103445b6b2cec3f5c0a81469e5", "size": 296, "vsize": 296, "version": 1, "locktime": 0, "vin": [ { "txid": "b7e225d9988796f26c2fdd672795b2d27c667d298cf1db169cdf11ab8b064844", "vout": 1, "scriptSig": { "asm": "304402202254f7da7c3fe2bf2a4dd2c3e255aa3ad61415550f648b564aea335f8fcd3d92022062eab5c01a5e33eb726f976ebd3b35d3991f8a45da56d64e1cd3fd5178f8c9a6[ALL] 02effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa 17753d68747470733a2f2f676f6f2e676c2f626d564575777576a9148911455a265235b2d356a1324af000d4dae0326288ac", "hex": "47304402202254f7da7c3fe2bf2a4dd2c3e255aa3ad61415550f648b564aea335f8fcd3d92022062eab5c01a5e33eb726f976ebd3b35d3991f8a45da56d64e1cd3fd5178f8c9a6012102effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa3217753d68747470733a2f2f676f6f2e676c2f626d564575777576a9148911455a265235b2d356a1324af000d4dae0326288ac" }, "sequence": 4294967295 } ], "vout": [ { "value": 0.00000600, "n": 0, "scriptPubKey": { "asm": "OP_DUP OP_HASH160 a4fdb3ce954a3cbb49fdfc9df1712bf72749c9e7 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a914a4fdb3ce954a3cbb49fdfc9df1712bf72749c9e788ac", "reqSigs": 1, "type": "pubkeyhash", "addresses": [ "mvZM34fU6wdDqF3gKd2tYA67vjWvwBHbDU" ] } }, { "value": 0.00000000, "n": 1, "scriptPubKey": { "asm": "OP_RETURN 4f410100019f8d0600", "hex": "6a094f410100019f8d0600", "type": "nulldata" } }, { "value": 0.00039400, "n": 2, "scriptPubKey": { "asm": "OP_DUP OP_HASH160 a4fdb3ce954a3cbb49fdfc9df1712bf72749c9e7 OP_EQUALVERIFY OP_CHECKSIG", "hex": "76a914a4fdb3ce954a3cbb49fdfc9df1712bf72749c9e788ac", "reqSigs": 1, "type": "pubkeyhash", "addresses": [ "mvZM34fU6wdDqF3gKd2tYA67vjWvwBHbDU" ] } } ], "blockhash": "0000000000000349ade641e1d0b0615cd8535d7790690e2d8b501907ec30b6c7", "confirmations": 307, "time": 1475927768, "blocktime": 1475927768 }
ブロックチェーン上のトランザクションからAsset Definition URLを確認する
MarkerOutput
のmetadata
を使う方法であれば、発行トランザクションがブロックチェーンに記録されると、そのMarkerOutput
をパースすればURLは分かるが、P2SHを使う場合はアセット発行トランザクションの入力に使ったP2SHのUTXOのredeem scriptにURLが定義されているので、その入力のscriptSigをパースする。(redeem scriptはそのUTXOが使用される際、そのUTXOを参照する入力のscriptSigの一部として公開される。)
↑の発行トランザクションの最初の入力がP2SHのアセット発行トランザクションなので、この中にredeem scriptが含まれている。そのscriptSigは以下のように3つのデータからなり
"asm": "304402202254f7da7c3fe2bf2a4dd2c3e255aa3ad61415550f648b564aea335f8fcd3d92022062eab5c01a5e33eb726f976ebd3b35d3991f8a45da56d64e1cd3fd5178f8c9a6[ALL] 02effb2edfcf826d43027feae226143bdac058ad2e87b7cec26f97af2d357ddefa 17753d68747470733a2f2f676f6f2e676c2f626d564575777576a9148911455a265235b2d356a1324af000d4dae0326288ac",
最初が署名、2つ目が公開鍵で、最後がredeem scriptになる。このredeem scriptをパースすると
redeem_script = Bitcoin::Script.new('17753d68747470733a2f2f676f6f2e676c2f626d564575777576a9148911455a265235b2d356a1324af000d4dae0326288ac'.htb) puts redeem_script.to_string > 753d68747470733a2f2f676f6f2e676c2f626d56457577 OP_DROP OP_DUP OP_HASH160 8911455a265235b2d356a1324af000d4dae03262 OP_EQUALVERIFY OP_CHECKSIG
となり先頭のデータがAsset Definition URL
になるので、これをデコードすればURLが入手できる。
今回openassets-rubyでも、後者のP2SHを使ったAsset Definition URL
のパースをできるようにしたので、対象アセットのUTXOを持つノードでlist_unspentを実行するとAsset Definition URL
も取得できる
// api.list_unspentの実行結果↓ ... { "txid": "a98b000f28c9e8251925a1225832edf1c2992e103445b6b2cec3f5c0a81469e5", "vout": 0, "confirmations": 310, "address": "mvZM34fU6wdDqF3gKd2tYA67vjWvwBHbDU", "oa_address": "bX6XEHEUoaRWuhVD3NF95C1zhGLh6qqqk5d", "script": "76a914a4fdb3ce954a3cbb49fdfc9df1712bf72749c9e788ac", "amount": "0.00000600", "asset_id": "oMb2yzA542yQgwn8XtmGefTzBv5NJ2nDjh", "asset_quantity": "99999", "asset_amount": "9999.9", "account": "p2sh asset", "asset_definition_url": "https://goo.gl/bmVEuw", "proof_of_authenticity": false, "output_type": "issuance", "solvable": true, "spendable": true }, ...
1点気になるのが、CoinprismのエクスプローラでURLが確認できないこと。P2SHのAsset Definition
に対応していないのか?それともこちらのURLのエンコード方法が間違っているのか?
P2SHを使ってデータを記録するということ
Open Assets Protocolに限った話ではなく、ブロックチェーン上に任意のデータを記録する場合、よく用いられるのがOP_RETURNを使う方法だ。実際Open Assets ProtocolもトランザクションにMarker Output
を付与するのにOP_RETURN
を使っている。
ただOP_RETURN
で付与できるデータサイズは80バイトであり、余裕あるサイズではない。
それに比べて、redeem scriptのサイズは520バイトでプッシュできるデータ量が多い。ただ、OP_RETURN
が出力で使われるのに対し、redeem scriptが明らかになるのは入力であるという違いがある。このためOP_RETURN
を使う方法とはデータ公開のタイミングが異なることを注意する必要はあるが、記録データ量は格段に多いので、redeem scriptを使ったデータ記録というアプローチもありだと思う。