用于验证 Taproot 资产转移的虚拟机执行环境
该文档描述了用于验证 Taproot Asset (资产)转移的虚拟机执行环境,该环境使用 asset_script_version 版本 1。
forked from bitcoin/bips
折叠文件树
文件
bip-tap
搜索此仓库
/
bip-tap-vm.mediawiki
复制路径
BlameMore 文件操作
BlameMore 文件操作
最近提交


和
bip-tap vm: specify that minting transition witnesses must be verified
2023年10月16日
bd3cdc1 · 2023年10月16日
历史
打开提交详情
292 行 (226 loc) · 14.1 KB
/
bip-tap-vm.mediawiki
顶部
文件元数据和控制
-
预览
-
代码
-
Blame
292 行 (226 loc) · 14.1 KB
复制原始文件
下载原始文件
大纲
编辑和原始操作
BIP: ???
Layer: Applications
Title: Taproot Asset Script v1
Author: Olaoluwa Osuntokun <laolu32@gmail.com>
Comments-Summary: No comments yet.
Comments-URI: https://git
Status: Draft
Type: Standards Track
Created: 2021-12-10
License: BSD-2-Clause
| ## 目录<br>Permalink: 目录<br>- 摘要<br>- 版权<br>- 动机<br>- 设计 <br> - 规范 <br> - 映射输入<br> - 映射输出<br> - 验证状态转换<br>- 测试向量<br>- 向后兼容性<br>- 参考实现 |
摘要
本文档描述了虚拟机执行环境,用于验证使用
asset_script_version 为 1 的 Taproot Asset 转账。
本文档中描述的执行环境是 BIP 341 和 342 中定义的 taproot 验证规则的略微修改版本。给定一个 Taproot Asset,
一个或多个要花费的 Taproot Asset 叶子(输入)和要创建的资产叶子,将创建一个“虚拟”taproot 比特币交易。这个交易
是一个 1 输入 1 输出的交易,它使用 merkle sum tree 提交到输入和输出集。完成此映射后,验证将正常进行。
版权
本文档基于 2-clause BSD 许可。
动机
Taproot Asset 覆盖允许使用几乎任意的虚拟机来验证系统内的转账。为了缩小协议的初始版本范围,我们描述了一种利用现有比特币脚本虚拟机的方法,允许我们继承一组基线的表达能力,同时允许实施者重用现有的工具和库。
设计
Taproot Asset asset_script_version 1 将 Taproot Asset 输入和输出集映射到“虚拟”比特币交易。
输入和输出集使用普通的 merkle sum tree 提交到单个 1 输入 1 输出的交易中
(TODO(roasbeef): 这里完全没有包含非包含吗??)。
通过 augmented merkle tree 的 merkle-sum 不变量,验证者能够通过断言已提交的输入总和等于已提交的输出总和来强制执行资产的非膨胀。一旦验证了此不变量,执行将按照 BIP 341+342 验证规则正常恢复,并附加可能导致验证提前失败的执行前检查。
规范
单个 1 输入 1 输出的交易用于将 Taproot Asset 状态转换状态压缩为恒定大小的交易。给定一个 Taproot Asset 承诺(存在于 taproot 输出中)及其有效的 opening,先前的资产 ID 集被压缩为单个输入,并且当前的 split_commitment 用于压缩输出状态。
状态转换验证可能在单个交易中采用一个或多个资产叶子(叶子存在于不同的输出中)。当存在单个叶子时,状态转换中没有发生拆分,或者该资产是 collectible。当指定两个或更多叶子时,除一个叶子之外的所有叶子都是由于 Taproot Asset 层级的拆分事件而产生的拆分。在这种情况下,拆分承诺证明以及创建拆分的状态转换的有效性都经过验证。
映射输入
输入映射仅针对指定 prev_asset_witnesses 的状态转换执行。
给定一组输入,每个输入都由 prev_asset_input 标识,输入承诺(用作先前的输出)的构建方式如下:
- 按照 bip-tap-ms-smt 中的规定初始化一个新的空 MS-SMT 树。
- 对于
prev_asset_witnesses字段中标识的每个 Taproot Asset 输入 c_i:- 如果资产输入在 witness 中具有
split_commitment,则需要在序列化步骤之前将其删除。 - 以 TLV 格式序列化引用的先前资产叶子(由
prev_outpoint || prev_asset_id || prev_asset_script_key标识)。- 对于 minting 交易,使用清空
prev_asset_witnesses的输出叶子的副本,以及以下修改:- 如果铸造的资产具有 group key,则复制的叶子的
asset_script_key应设置为等于该 group key。这确保状态转换验证在验证花费时使用 group key。 - 如果资产没有 group key,则
asset_script_key字段应为空白。这将使状态转换验证短路,从而允许铸造不支持发行的资产。
- 如果铸造的资产具有 group key,则复制的叶子的
- 这是为了确保我们也可以为 minting 交易获得完整的虚拟 tx 映射。
- 对于 minting 交易,使用清空
- 如果资产输入在 witness 中具有
- 将此叶子插入到 MS-SMT 树中,key 为
prev_id_identifier,值为序列化的叶子,总和值为叶子中包含的资产金额。 - 获取从树创建和 root 摘要计算得出的 root 哈希
input_root和总和值input_asset_sum。 - 让序列化的 36 字节 MS-SMT root 的哈希成为虚拟执行交易的唯一先前 outpoint (txid)。
通过上述例程,我们将输入集映射到 MS-SMT 树,该树还提交到任何给定资产的总花费金额。在验证期间,由于可能存在多个输入 witness,因此在验证期间,每个输入的 asset_witness 都用作初始 witness 堆栈。
请注意,我们在此统一输入承诺中没有映射 relative_lock_time 字段。相反,我们将在验证/签名过程中映射此字段,这将启用每个输入相关的相对和绝对锁定时间的存在。
以下算法实现了完全状态转换验证所需的输入映射:
make_virtual_input(prev_inputs: map[PrevOut]TaprootAssetLeaf) -> (MerkleSumRoot, TxIn):
input_smt = new_ms_smt()
for prev_out, taproot_asset_leaf in prev_inputs:
leaf_bytes = taproot_asset_leaf.serialize_tlv()
input_smt.insert(key=prev_out, value=leaf_bytes, sum_value=taproot_asset_leaf.amt)
input_root = input_smt.root()
virtual_txid = sha256(input_root.hash || input_root.sum_value)
# We only only bind the virtual txid here. Below we'll modify the input
# index based on the ordering of this SMT.
# 我们只在这里绑定虚拟 txid。 下面我们将根据此 SMT 的排序修改输入索引。
return input_root, NewTxIn(NewOutPoint(txid=virtual_txid), nil)
映射输出
输出映射仅针对指定 prev_asset_witnesses 的状态转换执行。
给定一个 Taproot Asset 输出,以及其 split_commitment_root 中包含的任何关联输出,输出承诺的构建方式如下:
- 对于正常的资产转移:
- 令输出值为顶层以及拆分承诺 cohort 集上的所有
amt字段的总和,换句话说,split_commitment_root的最后 4 个字节。 - 令输出脚本为转换为 segwit v1 witness program (taproot) 的
split_commitment_root值的前 32 个字节。
- 令输出值为顶层以及拆分承诺 cohort 集上的所有
- 对于 collectible 资产转移
- 令输出值正好为 1(因为与 collectible 相关的每个 TLV 叶子只能将相同的 collectible 转移到另一个叶子上)。
- 令输出脚本为 MS-SMT 树的前 32 个字节,该 MS-SMT 树具有 collectible 的序列化 TLV 叶子的单个元素。
- 此单个值的 key 为
sha256(asset_key_family || asset_id || asset_script_key)。如果未指定asset_key_family字段,则应使用 32 个字节的零来代替。
- 此单个值的 key 为
以下算法实现了完全状态转换验证所需的输出映射:
make_virtual_txout(leaf: TaprootAssetLeaf) -> (MerkleSumRoot, TxOut):
match leaf.asset_type:
case Normal:
tx_out = NewTxOut(
pk_script=[OP_1 OP_DATA_32 leaf.split_commitment_root.hash],
value=leaf.split_commitment_root.sum_value,
)
return leaf.split_commitment_root, tx_out
case Collectible:
output_smt = new_ms_smt()
output_smt.insert(
key=sha256(leaf.asset_key_family || leaf.asset_id || leaf.asset_script_key)
value=leaf.serialize_tlv(),
sum_value=1,
)
witness_program = output_smt.root_hash()
tx_out = NewTxOut(
pk_script=[OP_1 OP_DATA_32 witness_program],
value=1,
)
return output_smt.root, tx_out
验证状态转换
如果状态转换指定了 prev_asset_witnesses 字段,那么
一旦输入和输出集映射到我们的虚拟比特币交易(创建一个具有单个输入和输出的 v2 比特币交易),
验证将按照 BIP 341+342 正常进行,并进行以下修改:
- 如果
input_asset_sum不完全等于output_asset_sum,则验证必须失败。 - 对于引用的
prev_asset_witnesses集中的每个prev_input:- 如果引用的输入叶子的
asset_type没有映射到花费该输入的 Taproot Asset 叶子的asset_type,则验证必须失败。 - 基于上面的输入和输出映射构建一个单输入单输出比特币交易。
- prev out 输入索引应该是每个输入的
prev_id_identifier字段的字典索引。 - 先前的公钥脚本应该是当前先前输入的
asset_script_key,映射到 v1 segwit witness program (taproot)。 - 每个包含的输入的输入值是要花费的先前 Taproot Asset 输出的
amt字段。 - 如果输入存在
relative_lock_time字段,则将序列号设置为该字段。
- prev out 输入索引应该是每个输入的
- 如果引用的输入叶子的
- 如果输入 TLV 叶子存在
lock_time,则将交易的锁定时间设置为该叶子的锁定时间。 - witness 中包含的所有签名长度必须正好为 64 个字节,这将触发
SIGHASH_DEFAULT评估。 - 如果
asset_script_key为空白,则asset_group_key必须为空白,并且所有 witness 必须为空白。在这种情况下,验证成功,因为这只是没有 emission 的资产的创建/minting 交易。 - 如果每个 Taproot Asset 输入和输出的
asset_id值不相同,则验证必须失败。- 或者,断言每个输入和输出都引用相同的
asset_family_key字段。
- 或者,断言每个输入和输出都引用相同的
- 执行外部锁定时间和相对锁定时间验证:
- 如果存在
relative_lock_time字段,如果引用的 TLV 叶子的输入 age 小于relative_lock_time,则验证必须失败。 - 如果存在
lock_time字段,如果包含交易的区块的区块高度小于lock_time,则验证必须失败。
- 如果存在
- 根据 BIP 341+342 规则验证交易。
我们在此级别显式地实现锁定时间语义,因为比特币本身的上下文中的序列和锁定时间字段是从将新区块连接到主链末尾的 PoV 进行验证的。
否则,如果状态转换仅指定 split_commitment_proof,则:
- 如果要验证的 Taproot Asset 输出仅指定
split_commitment_proof且没有显式输入,则必须提供并验证输出的有效包含证明。 - 如果证明无效,则验证必须失败。
- 给定“父”拆分,执行输入+输出映射并使用上面的逻辑验证状态转换。
以下算法实现了顶级 Taproot Asset 叶子以及通过拆分承诺创建的叶子的验证:
verify_taproot_asset_state_transition(leaf: TaprootAssetLeaf, leaf_split: TaprootAssetLeaf) -> bool
if is_valid_issuance_txn_no_group_key(leaf):
return true
if leaf_split is not None:
if leaf is None:
return false
if !verify_split_commitment(leaf.split_commitment_root,
leaf_split.split_commitment_proof):
return false
input_smt, tx_in = make_virtual_input(leaf.prev_inputs)
output_smt, tx_out = make_virtual_txout(leaf)
if input_smt.sum_value != output_smt.sum_value:
return false
virtual_tx_template = NewTx([tx_in], [tx_out])
for input in range leaf.prev_inputs:
if input.asset_type != leaf.asset_type:
return false
match input.asset_id:
case AssetID:
if input.asset_id != leaf.asset_id:
return false
case KeyFamily:
if input.asset_key_family != leaf.asset_key_family:
return false
virtual_tx = virtual_tx_template.clone()
if !parse_valid_schnorr_sigs(input.asset_witness):
return false
virtual_tx.tx_in[0].witness = input.asset_witness
virtual_tx.tx_in[0].prev_out.index = input_smt.leaf_index_of(input)
prev_pk_script = OP_1 OP_DATA_32 input.asset_script_key
input_value = input.amt
if input.relative_lock_time != 0:
virtual_tx.tx_in[0].sequence = relative_lock_time
input_age = conf_input_age(input)
if num_confs(input) < input_age:
return false
if input.lock_time != 0:
virtual_tx.lock_time = leaf.lock_time
block_height = env.block_height()
if block_height < virtual_tx.lock_time:
return false
vm = new_script_vm(
prev_pk_script=prev_pk_script, tx=virtual_tx, input_index=0,
input_amt=input_value,
)
if !vm.Execute():
return false
return true
测试向量
验证状态转换 的测试向量可以在这里找到:
测试向量由 Taproot Assets GitHub 仓库中的单元测试 自动生成。
向后兼容性
参考实现
github.com/lightninglabs/taproot-assets/tree/main/vm
- 原文链接: github.com/Roasbeef/bips...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~