Skip to content

方法概述

代币以单独的 UTXO 存储,类似于原生的聪(satoshis)。我们使用契约(covenants)来确保代币在所有后续交易中保持其特有的形式。更具体地说,我们使用递归契约(recursive covenants)来维护代币状态。

我们将逐步构建 CAT 代币智能合约。从一个简单但不安全的实现开始。

初次尝试

alt text 在上图中,当前交易 curTx 将来自两个父交易 parent0Txparent1Tx 的代币合并。每个代币输出的状态包括:

  • addr:所有者的地址
  • amt:输出中代币的数量

它们的脚本是相同的,并且必须满足代币脚本 T 中规定的所有条件/断言1alt text

  1. 条件(1)确保所有者 A 授权合并。
  2. (2) 和(3)确保脚本从父级传递到当前的花费/赎回交易,接着传递到其子级,依此类推,即递归契约。
  3. 条件(4)确保合并后代币数量保持不变,不会凭空创建新代币。

一次攻击

代币脚本 T 存在一个安全漏洞:对手可以将两种不同类型的代币合并。下图示例了这种攻击。交易 parent0Txparent1Tx 分别铸造了 22 和 11 单位的不同代币。这类似于从 coinbase 交易中仅新发出的比特币。

curTx 将它们合并为同一代币的 33 单位。 alt text 注意,代币脚本 T 中的所有条件都已满足。

为防止此攻击,我们为每个代币分配一个唯一标识符。一个直接的方法是使用铸造交易所花费的输出点(outpoint)作为 tokenId,即 txid_vout。我们称包含该输出点的交易为代币的创世交易(例如,T1T2)。由于每个创世输出点在全局上是唯一的,我们拥有一个唯一的 tokenId。我们将 tokenId 添加到其状态中,并在 T 中增加两个条件。 alt text 这些条件确保 tokenId 从父级传递到子级、孙级等。代币输出的所有后续花费必须嵌入 tokenId,以确保代币的整个生命周期。这项技术有效地“标记”了代币,并确保只有相同类型/标记的代币可以合并。 alt text 通过这些补充,之前的攻击被阻止,因为条件(2)被违反。

拼图的最后一块

我们还没完成。注意,T2 中的脚本 S2 被评估,但 T 没有在 parent1Tx 中铸造代币时被评估。T 仅在铸造的代币被花费时评估。因此,攻击者可以选择任意值作为 tokenId。例如,她可以将 tokenId 设置为 T1_0,而不是 T2_0,并在 parent1Tx 中伪造具有 ID T1_0 的 11 单位代币。这些代币可以与 parent0Tx 中的 22 个有效代币合并,绕过所有先前的条件检查。 alt text 为解决此问题,我们在代币脚本 T 中添加了另一个条件检查,以验证 tokenId 是否如实设置为创世输出。 alt text 为了了解其工作原理,让我们看下图。假设我们位于左侧箭头指示的第三个交易 curTx。由于脚本 S 不是 T,我们知道 grandparentTx 是创世交易,并强制 parentTx 中的 tokenId 设置为 T1_0。从创世开始的所有后代交易在铸造时通过算法设置了相同的 tokenId。假设我们位于右侧箭头指示的第四个交易,我们跳过上述条件检查,因为祖父级和父级的脚本是相同的。对于第 1000 代交易也是如此。在任何代币交易中,其祖父级只能是以下两种之一:

  1. 代币创世交易
  2. 相同的代币交易:相同的脚本和 tokenIdalt text 上述攻击现已被阻止。 alt text 攻击者仍然可以伪造代币,但这些代币不可花费,使伪造变得无效。

脚注

TxoTx.outputs 的缩写。