Appearance
方法概述
代币以单独的 UTXO 存储,类似于原生的聪(satoshis)。我们使用契约(covenants)来确保代币在所有后续交易中保持其特有的形式。更具体地说,我们使用递归契约(recursive covenants)来维护代币状态。
我们将逐步构建 CAT 代币智能合约。从一个简单但不安全的实现开始。
初次尝试
在上图中,当前交易 curTx 将来自两个父交易 parent0Tx 和 parent1Tx 的代币合并。每个代币输出的状态包括:
addr:所有者的地址amt:输出中代币的数量
它们的脚本是相同的,并且必须满足代币脚本 T 中规定的所有条件/断言1: 
- 条件(1)确保所有者 A 授权合并。
- (2) 和(3)确保脚本从父级传递到当前的花费/赎回交易,接着传递到其子级,依此类推,即递归契约。
- 条件(4)确保合并后代币数量保持不变,不会凭空创建新代币。
一次攻击
代币脚本 T 存在一个安全漏洞:对手可以将两种不同类型的代币合并。下图示例了这种攻击。交易 parent0Tx 和 parent1Tx 分别铸造了 22 和 11 单位的不同代币。这类似于从 coinbase 交易中仅新发出的比特币。
curTx 将它们合并为同一代币的 33 单位。
注意,代币脚本 T 中的所有条件都已满足。
为防止此攻击,我们为每个代币分配一个唯一标识符。一个直接的方法是使用铸造交易所花费的输出点(outpoint)作为 tokenId,即 txid_vout。我们称包含该输出点的交易为代币的创世交易(例如,T1 和 T2)。由于每个创世输出点在全局上是唯一的,我们拥有一个唯一的 tokenId。我们将 tokenId 添加到其状态中,并在 T 中增加两个条件。
这些条件确保 tokenId 从父级传递到子级、孙级等。代币输出的所有后续花费必须嵌入 tokenId,以确保代币的整个生命周期。这项技术有效地“标记”了代币,并确保只有相同类型/标记的代币可以合并。
通过这些补充,之前的攻击被阻止,因为条件(2)被违反。
拼图的最后一块
我们还没完成。注意,T2 中的脚本 S2 被评估,但 T 没有在 parent1Tx 中铸造代币时被评估。T 仅在铸造的代币被花费时评估。因此,攻击者可以选择任意值作为 tokenId。例如,她可以将 tokenId 设置为 T1_0,而不是 T2_0,并在 parent1Tx 中伪造具有 ID T1_0 的 11 单位代币。这些代币可以与 parent0Tx 中的 22 个有效代币合并,绕过所有先前的条件检查。
为解决此问题,我们在代币脚本 T 中添加了另一个条件检查,以验证 tokenId 是否如实设置为创世输出。
为了了解其工作原理,让我们看下图。假设我们位于左侧箭头指示的第三个交易 curTx。由于脚本 S 不是 T,我们知道 grandparentTx 是创世交易,并强制 parentTx 中的 tokenId 设置为 T1_0。从创世开始的所有后代交易在铸造时通过算法设置了相同的 tokenId。假设我们位于右侧箭头指示的第四个交易,我们跳过上述条件检查,因为祖父级和父级的脚本是相同的。对于第 1000 代交易也是如此。在任何代币交易中,其祖父级只能是以下两种之一:
代币创世交易相同的代币交易:相同的脚本和tokenId
上述攻击现已被阻止。
攻击者仍然可以伪造代币,但这些代币不可花费,使伪造变得无效。
脚注
Txo 是 Tx.outputs 的缩写。