前回の記事でTaroのアセットツリーの構造を確認したので↓
techmedia-think.hatenablog.com
今回は、Taroでのアセット発行の仕組みをRubyで実装しながらみていく。
tarocliを使ったアセットの発行
tarocliのassets mint
コマンドを利用するとアセットを新規発行することができる。実際にtestnetで発行してみたのが↓
$ tarocli assets mint --type normal --name testcoin --supply 1000 --meta "metadata for test" --skip_batch { "batch_key": "02fe3155363518765636316775fec96b57e454c3dd50ce19d18da5a1f9cf91b3a7" } $ tarocli assets list { "assets": [ { "version": 0, "asset_genesis": { "genesis_point": "af2ed8bc93a28cba80bc3c8c55fc554bb7cb3f9071f59f87b83724d4e8b92ea9:1", "name": "testcoin", "meta": "6d6574616461746120666f722074657374", "asset_id": "9333d8360be674dfae320b7ae16bd3b618726637fad107a1d2b248a3083061ca", "output_index": 0, "genesis_bootstrap_info": "a92eb9e8d42437b8879ff571903fcbb74b55fc558c3cbc80ba8ca293bcd82eaf000000010874657374636f696e116d6574616461746120666f7220746573740000000000", "version": 0 }, "asset_type": "NORMAL", "amount": "1000", "lock_time": 0, "relative_lock_time": 0, "script_version": 0, "script_key": "026e552eef52663dcf984ad1443660a66923aca4c8b77240fb83114dbb0ea91269", "asset_family": null, "chain_anchor": { "anchor_tx": "02000000000101a92eb9e8d42437b8879ff571903fcbb74b55fc558c3cbc80ba8ca293bcd82eaf0100000000ffffffff02e8030000000000002251204a8b4d59fede637b63c2473c06234615a7c82406912954f744e358e74922e254e9e500000000000016001496c99700a2d53b57904468a77e0c3db3da7463660140f5af40e5a9bcc96a57aa0d7a696e05f3752e0b150dd414fb0f5796e9b4ff625b3efaf06dc90733d0b6b949126d3a0b24bc1db796e54984234654e6a9c4e72a2000000000", "anchor_txid": "57ffcf286d5f1ec8a3168d48e7ec861bda2edaec87ddba4784d81e46a3a48a47", "anchor_block_hash": "0000000000000000000000000000000000000000000000000000000000000000", "anchor_outpoint": "478aa4a3461ed88447badd87ecda2eda1b86ece7488d16a3c81e5f6d28cfff57:0", "internal_key": "02fe3155363518765636316775fec96b57e454c3dd50ce19d18da5a1f9cf91b3a7" } } ] }
※ anchor_txidは、エンディアンが逆になってるので要注意
今回は、この発行処理の中身を見ていく。
アセットの発行手順
アセットを発行するということは、Taprootベースのアセット発行用のTaprootのアウトプットを作成するということで、Taroでは以下の手順でアセット発行用のTaprootアウトプットを作成する。
- ウォレットが保持している任意のUTXOをピックアップし、アセット発行に使用するOutPointを選択する。
- 1をgenesis_pointとして、asset_tagとasset_metaからアセットIDを計算する。
- 新規発行するアセットのアセットツリーを構築する(ツリー構造については↑の記事参照)。
- 新規発行する2のアセットIDのアセットツリー(2階層めのツリー)のリーフノードを作成する(
asset_leaf || leaf_sum
)。 - 生成したリーフを含むMS-SMTのルートハッシュを計算する。
- 計算したルートを使って、アセットツリー(1階層めのツリー)であるMS-SMTのリーフを作成する(
taro_version || asset_id_tree_root || asset_sum
)。 - 作成したリーフを持つMS-SMTのルートハッシュを計算する。
- 新規発行する2のアセットIDのアセットツリー(2階層めのツリー)のリーフノードを作成する(
- Taprootの内部鍵用の鍵を生成する。
- アセットツリーのルートハッシュをリーフとしたTapscriptのツリーを作成し、内部鍵と合わせてTaprootアウトプットを作成する。
このプロセスを詳しくみていく。
アセットIDの計算
まず最初にアセットIDは、前回の記事から、
asset_id = sha256(genesis_outpoint || asset_tag || asset_meta || output_index || asset_type)
として計算されるので、genesis_point
、name
(=タグ)、meta
、output_index
、asset_type
(NORMAL = 0)から以下のように計算できる(bitcoinrbを使用)。
require 'bitcoin' genesis_point = Bitcoin::OutPoint.from_txid('af2ed8bc93a28cba80bc3c8c55fc554bb7cb3f9071f59f87b83724d4e8b92ea9', 1) tag = Bitcoin.sha256('testcoin') asset_meta = Bitcoin.sha256("6d6574616461746120666f722074657374".htb) # 6d6574616461746120666f722074657374 = "metadata for test" output_index = [0].pack('I>') type = [0].pack('C') payload = genesis_point.to_payload + tag + asset_meta + output_index + type asset_id = Bitcoin.sha256(payload) => "9333d8360be674dfae320b7ae16bd3b618726637fad107a1d2b248a3083061ca"
アセットツリーの構築
Taroのアセットツリーは、↑の記事に書いたように2階層のツリーで構成されている。
2階層めのツリー
まず最初に、TaroのアセットID毎のツリー(MS-SMT)を作成する。このツリーに挿入するKey-Valueのペアは、
- Key:Asset Script Key(アセットの所有者の管理化にある公開鍵)
- Value:
asset_leaf || leaf_sum
をリーフの値とし、Sumとして発行量leaf_sum
を持つリーフノード。
KeyのAsset Script Keyは、↑のtarocli
で発行したアセットの場合↓の公開鍵になる。
"script_key": "026e552eef52663dcf984ad1443660a66923aca4c8b77240fb83114dbb0ea91269"
Valueであるリーフノードは、asset_leaf
とleaf_sum
結合したデータで、asset_leaf
はアセットに関連する項目をLightningでおなじみのTLVフォーマットで定義した内容になる(現状、こことかFamily Keyのバイト数とか一部BIPドラフトとtarod
のα版とでどうも仕様のズレがある)。
↑の単一のアセットの発行のみであれば、空のMS-SMTを作成し、↑のKey-Valueのデータを1つツリーに挿入してアセットツリーを作成すればいい。
このツリーは、あるアセットの各所有者とその所有量を管理するツリーとなる。
1階層めのツリー
続いて↑の記事の1階層めのツリー(MS-SMT)を作成する。このツリーに挿入するKey-Valueのペアは、
- Key:アセットID(Asset Family Keyが設定されている場合は、その公開鍵のSHA-256値)
- Value:リーフノードの値として
taro_version || asset_id_tree_root || asset_sum
を持ち、アセットIDツリーのsum値を持つリーフノード。ここでasset_id_tree_root
は先程作成したツリーのルートハッシュで、asset_sum
は先程作成したツリーのルートのsum値。
つまり、1階層めのツリーは、アセットIDをキーにそのアセットの所有者及び所有量がコミットされている2階層めのツリーに対するコミットを行うツリーとなる。
Taprootアウトプットの作成
アセットツリーが構成できたら、それにコミットするTaprootのアウトプットを作成する。Taprootの作成には、内部鍵(Internal Key)とTapscriptのツリーが必要になる。
Taprootの内部鍵
Taprootの内部鍵はウォレットが作成することになる。今回tarod
が生成した内部鍵は↑の出力結果から↓
"internal_key": "02fe3155363518765636316775fec96b57e454c3dd50ce19d18da5a1f9cf91b3a7"
Tapscriptツリー
続いて、↑のアセットツリーにコミットするTapscriptのツリーを構成する。今回みたいな単一のアセットの発行では、条件となるScriptブランチが1つのTapscriptツリーを構成すればいい。
この時、ツリーに挿入するリーフのスクリプトは以下のペイロードのスクリプトになる。
leaf_version || taro_marker || taro_version || asset_tree_root || asset_tree_sum
version
は、アセットツリー内のアセットのバージョンの最大値(現状0)taro_marker
は、マーカー用のタグで、値はsha256("taro")
asset_tree_root
は↑の1階層めのツリーのルートハッシュasset_tree_sum
は↑の1階層めのツリーのsum値
↑から分かるように、Bitcoin Scriptとして有効なデータではなさそう。つまり、これをそのままTaprootのTapscriptとして評価するような使い方はしないということかな。
今回の発行の仕組みでは、単純にこの単一のスクリプトをリーフとしたTapscriptのツリーを作って、そのルートハッシュと、↑の内部鍵を使ってTaprootのアウトプットを作ればいい。
サンプル実装
試しにtarocli
が作成したアセット発行トランザクションと同じTaprootアウトプットをRubyで書いてみた(個別の処理を書いてくと長くなるので、tarorbというライブラリとして実装)。
require 'taro' Bitcoin.chain_params = :testnet # 発行に使用するUTXOの指定 genesis_point = Bitcoin::OutPoint.from_txid( "af2ed8bc93a28cba80bc3c8c55fc554bb7cb3f9071f59f87b83724d4e8b92ea9", 1 ) # アセットの生成 genesis = Taro::Genesis.new( prev_out: genesis_point, tag: "testcoin", metadata: "metadata for test", output_index: 0 ) asset_script_key = Bitcoin::Key.new( pubkey: "026e552eef52663dcf984ad1443660a66923aca4c8b77240fb83114dbb0ea91269" ) asset = Taro::Asset.new(genesis, 1000, 0, 0, asset_script_key) asset_id = asset.genesis.id # ↑で計算したのと同じアセットID # 2階層めのツリーを構築 asset_commitment = Taro::AssetCommitment.new([asset]) # 1階層めのツリーを構築 taro_commitment = Taro::TaroCommitment.new([asset_commitment]) # 内部鍵 internal_key = Bitcoin::Key.new( pubkey: "02fe3155363518765636316775fec96b57e454c3dd50ce19d18da5a1f9cf91b3a7" ) #Taprootのアウトプットを作成 taproot_script_pubkey = taro_commitment.to_taproot(internal_key) taproot_script_pubkey.to_addr # tarocliが発行したTxと同じTaprootのアドレス
最後のアドレスはtarocli
のanchor_outpoint
のUTXO(478aa4a3461ed88447badd87ecda2eda1b86ece7488d16a3c81e5f6d28cfff57:0)と同じアドレスになる。
以上が、Taroのアセット発行の手順。新規発行の場合は、アセットの所有者の検証などないので、次はアセットを転送する際の検証処理など見ていきたい。