来源:极客 web3
作为比特币的核心设计原则之一,UTXO 模型在诞生之日起就成为了区块链领域中一种重要的技术范式。它在保障交易安全性和可追溯性方面发挥了重要作用,同时提供了传统账户余额模型以外的另一条道路。随着近些年区块链技术不断经历更新迭代,UTXO 模型本身也在不断地演化与扩展(如 eUTXO、cell、Strict access list 等)。
本文以学习和了解 UTXO 模型为目的,用浅显易懂的方式,简单梳理从 BTC 到 Sui、Cardano 和 Nervos、Fuel 各自的 UTXO 模型及实现方式,使其更好理解。
什么是 UTXO?
可以通过一个例子理解 UTXO 模型:
假设有两个人,Alice 和 Bob,他们原本各有 5 块钱。之后,双方发生了冲突,Alice 被 Bob 抢走了 2 块钱。二人最终持有的金钱数额如下图所示:
不难看出,Alice 最终剩下 3 块钱,Bob 最终持有 7 块钱。这种小学加减法一样的记账方式频繁出现在银行系统中,被称为 “账户/余额模型”。其中,账户的余额作为单一的数值而存在。
如果用不同于账户模型的方式,比如 UTXO 表示 Alice 和 Bob 之间发生的财富转移,示意图则会变成不同的样子:
此时,Alice 还是剩 3 块钱,Bob 还是剩 7 块钱,但这 7 块钱并不是用一个单一数值表示的,而是被拆成了 “5 块钱” 和 “2 块钱”。这种反常规的方法是不是让人感到不太习惯?这就是特殊的记账方式—— UTXO 。
UTXO 英文全称 Unspent Transaction Output,指 “未被花费的输出”。在这种记账方式下,每笔链上交易会表现为 UTXO 的变化与转移。比如,在上文提到的交易事件中,Alice 最初拥有的 “5 块钱” 作为输入参数,被标记为 UXTO_0,之后会被销毁;同时,程序会生成 “2 块钱”(UTXO_1)和“3 块钱”(UTXO_2)作为输出参数,UTXO_1 将被转给 Bob,UTXO_2 将转回给 Alice,ALice 和 Bob 之间的财富转移以此完成。
实际上,在 UTXO 模型中,不存在 “账户” 和 “余额” 这两个明确的概念,UTXO 只是帮助交易执行的数据结构,它会记录自身代表的金额、与其相关的交易索引等信息。每个 UTXO 都代表一个可以被使用但未被使用的交易输入,具有确定的所有者。当一笔交易发生时,可以将某些 UTXO 作为输入,将其销毁后会产生新的 UTXO 作为交易输出结果。
这就是 Bitcoin 的记账方式:每次交易都会有旧的 UTXO 被销毁,新的 UTXO 被产生。被销毁的 UTXO 总金额等于新造的 UTXO 金额(其中某部分是给矿工的手续费)。这样一来,没有人可以凭空增发资金。
UTXO 模型和账户/余额模型的比较
假设有一批用户同时发起了大量交易请求,如果分别使用 UTXO 模型和账户/余额模型处理交易,情况会是怎样?
在账户/余额模型中,每个用户都拥有一个账户,其中记录着余额信息。有交易发生时,相应账户的余额要被更新,这涉及对其 “读” 和 “写” 的操作。可如果某两笔交易涉及同一个账户,往往会产生读写上的冲突,即状态争用,这是必须要避免的情况。
传统的数据库系统往往通过 “锁” 机制,解决对某部分数据的读写争用。在这种场景下,构成数据争用关系的多笔交易往往要排队,无法同时执行,这会使交易的处理效率下降。当有大量交易待处理时,上述情况可能会导致严重的性能瓶颈,彼此有数据争用关系的交易可能长时间处于等待状态,无法被快速处理。
相比于账户余额模型,比特币的 UTXO 模型可以更好的解决数据争用问题。因为在这种方式下,每笔交易的直接处理对象不再是某个“账户”,而是各个独立的 UTXO。由于不同的 UTXO 互不干扰,比特币网络中每笔交易都是互不干扰的。因此,比特币网络节点在处理大量的待处理交易时,可以同时处理多笔交易,无需使用 “锁”,这样可以大大提高系统的吞吐量和并发性能。
此外,UTXO 模型的加密钱包通常会在用户发起一笔交易后,生成一个新地址,这样可以实现隐私保护——要将交易和某个具体的人关联起来变得更为困难——相比之下,账户/余额模型由于使用固定的地址,更容易被关联性分析。
但 UTXO 也存在局限性,其设计初衷是实现简单的货币转移,不是处理复杂的业务逻辑,尽管可以用脚本语言进行一些简单的功能实现,如多签、时间锁等,但由于比特币的 UTXO 能记录的状态信息太简陋,使其在进行一些复杂操作时有心无力。
比特币 UTXO 的局限性间接推动了 “以太坊” 的诞生——Vitalik 作为 Bitcoin Magazine 最早的撰稿人之一,对比特币的缺点十分了解。而账户/余额模型不仅更容易为大多数人所理解,还可以解决 UXTO 难以处理富状态应用的困境,正如他在“以太坊白皮书”中所说的:
UTXO 可以是已使用或未使用;用于保存任何其他内部状态的多阶段合约或脚本是没有机会出现的。这使得多阶段期权合约、去中心化交易报价或两阶段加密承诺协议(这是安全计算赏金所必需的)难以创建。这也意味着 UTXO 只能用于构建简单的一次性合约,而不是去中心化组织等更复杂的“有状态”合约,使得元协议难以实现。二进制状态加之价值盲点也意味着另一个重要应用 — 提款限制 — 是不可能实现的。
UXTO 模型的应用、优化和扩展
在介绍各种对 UXTO 的应用和优化之前,首先要分析UTXO在保持其优势的同时有哪些提升点,简单总结为如下几点:
1. 对 UTXO 所存储状态的意义进行抽象。
2. 对状态的所有权进行抽象。
3. 解决共享 UTXO 的状态争用问题。
在 BTC 中,状态唯一的意义就是代币数量,而所有权通常用公钥来定义,至于状态争用,BTC 并不是为 dapp 而设计,所以也没有过多涉及。
Sui
Sui 为开发人员提供了两种对象类型:OwnedObject 和 SharedObject,前者相当于UTXO(更具体来说是 UTXO 的增强版),后者相当于账户/余额模型,两者可以同时使用,此处引用 Sui 技术文档的解释:
一个 Object 可以被共享,这意味着任何人都可以读取或写入该 Object。与可变的 OwnedObject (只能有一个写入者)相比,SharedObject 需要共识来对读取和写入进行排序。
在其他区块链中,每个 Object 都是共享的。然而,Sui 编程人员通常可以选择使用 OwnedObject、SharedObject 或两者的组合来实现特定的用例。这个选择可能对性能、安全性和实现复杂性产生影响。
在 Sui 中,Owned Objects 就类似于 UTXO,只有它的所有者 Owner 能对其进行操作,且 Object 都有版本号,“一个 object 的某个版本只能被它的 owner 花销一次”,所以,“一个 object 的某个版本” 实质就相当于 UTXO。
至于状态争用的问题,则可以通过特殊处理(局部排序,和 Fuel 类似) SharedObject 来实现。
Cardano
Cardano 使用 extended UTXO 模型,缩写为 eUTXO。eUTXO 支持更高的可编程性,同时兼有比特币 UTXO 模型的优点。
在 Cardano 中,状态的意义通过脚本进一步得到扩展,而其状态的所有权则通过更一般化的方式进行定义,同时使用 UTXO 集来尽量避免出现状态争用问题。具体概括,eUTXO 在两个方面有所加强:
1. eUTXO 模型中存在更一般化的地址,这些地址不仅仅可以基于公钥的哈希,还能基于任意逻辑定义在何种条件下可以花费 eUTXO,即可以对状态的所属权进行编程。
2. 除了地址和值之外,输出还可以携带(几乎)任意数据,即可以通过脚本对状态的意义进行编程。
具体而言,eUTXO 允许用户将类似JSON格式的任意数据添加到 UTXO中,该数据称为 Datum。Datum 允许开发人员为脚本提供类似状态的功能,它与特定的 UTXO 相关联。
同时,Cardano 上的交易可以携带与特定用户相关的参数,称为 Redeemer。Redeemer 允许交易发起者定义 UTXO 的使用方式,可以被 dapp 开发人员用于各种目的。
当一笔交易被验证时,验证脚本会使用 Datum、Redeemer 和包含交易数据的上下文进行操作,该脚本中会包含在满足条件时使用 UTXO 的逻辑。
需要注意的是,eUTXO 仍然是通过脚本来完成拓展任务的,和传统意义上的“智能合约”有着很大的差别(创始人 Charles Hoskinson 认为实际的名字应该叫“可编程验证器”,但“智能合约”这个说法更容易被市场所接受)。
Nervos
在 Nervos(即 CKB)中,状态的意义由 typescript 抽象,而其状态的所有权由lockscript 抽象,一个简单的 UTXO 优化模型——cell 代码如下:
pub struct CellOutput {
pub capacity: Capacity,
pub data: Vec<u8>,
pub lock: Script,
pub type_: Option<Script>, }
而对于状态争用问题,目前 CKB 推进研究的是 Open Transaction,用户可以提出一个部分 UTXO 指明交易目的,然后由撮合者撮合成完整的交易。
Nervos 的 cell 模型是 UTXO 的“一般化”版本,对其详细的科普 Jan 在 Nervos 论坛上如此解释:
Layer 1 的关注点在状态,以 Layer 1 为设计目标的 CKB 设计的关注点很自然就是状态。Ethereum 将交易历史和状态历史分为两个维度,区块和交易表达的是触发状态迁移的事件而不是状态本身,而 Bitcoin 协议中的交易和状态融合成了一个维度,交易即状态,状态即交易,正是一个以状态为核心的架构。
同时,CKB 想要验证和长久保存的状态,不仅仅是简单的数字(nValue),而是任何人们认为有价值的、经过共识的数据。显然 Bitcoin的交易输出结构满足不了这个需求,但是它已经给了我们足够的启发:只需要将 nValue 一般化,把它从一个存放整数的空间变成一个可以存放任意数据的空间,我们就得到了一个更加一般化的 “CTxOut”,或者叫 Cell。
在 Cell 里面,nValue 变成了 capacity 和 data 两个字段,这两个字段共同表示一块存储空间,capacity 是一个整数,表示这块空间有多大(以字节数为单位),data 则是保存状态的地方,可以写入任意的一段字节;scriptPubKey 变成了 lock,只是换了一个名字而已,表达的是这块共识空间的所有者是谁 - 只有能提供参数(例如签名)使得 lock 脚本成功执行的人,才能“更新”这个 Cell 中的状态。整个 CellOutput 占用的字节数必须小于等于 capacity。CKB 中存在着许许多多的 Cells,所有这些 Cell 的集合形成了 CKB 完整的当前状态,在 CKB 的当前状态中存储的是任意的共同知识,不再仅仅是某一种数字货币。
交易依然表示状态的变化/迁移。状态的变化,或者说 Cell 内容的“更新”实际上也是通过销毁和创建来完成的(并不是真的去修改原有 Cell 中的内容)。每一笔交易实际上都会销毁一批 Cells,同时创建一批新的 Cells;新创造的Cells会有新的所有者,也会存放新的数据,但是被销毁的 capacity 总和,总是大于等于新创建的 capacity 总和,由此保证没有人可以随便增发 capacity。因为 capacity可以转让,无法增发,拥有 capacity 等于拥有相应数量的共识状态空间,capacity 是CKB网络中的原生资产。Cell 的销毁只是把它标记为 “已销毁”,类似 Bitcoin 的 UTXO 从未花费变为已花费,并不是从区块链上删掉。每一个 Cell 只能被销毁一次,就像每一个 UTXO 只能被花费一次。
这样一个模型的特点是:
1. 状态是第一性的。
2. 所有者是状态的属性,每一份状态只有一个所有者。
3. 状态不断地被销毁和创建。
所以说,Cell 是 UTXO 的一般化(generalized)版本。
Fuel
Fuel 采用了基于 UTXO 优化的 Strict access list 模型,这种模型定义了一种新的UTXO——合约 UTXO。
正如上文所介绍过的,BTC 中的 UTXO 只有两个属性:币的数量和所有者,而合约 UTXO 则提供了更多的基础属性,包括:币的数量、合约 ID、合约代码哈希和存储根。
如果使用无状态执行模型,只有在合约 UTXO 中才需要合约代码哈希和存储根。在有状态执行模型中,合约 UTXO 可以省略这些字段,但需要单独的存储元素 UTXO 类型。UTXO ID(每个 UTXO 的唯一标识符,可以用作键值存储数据库中的键)是产生 UTXO 的输出点,或者其变体(例如,输出点及其字段的哈希)。
在这种模型中,合约 UTXO 和智能合约一样是任何人都可以调用的。
需要注意的是 Fuel 提供的是更为贴近智能合约的功能,而非脚本,而 UTXO 本身模型的限制使得基于VM去做应用时会有数不清的麻烦,最典型的就是 UTXO 的争用问题,一般来说有三种解决办法:一是在链下处理如 Rollup;二是先提前做好额外的排序工作,Fuel 采用的就是后者;三是刚刚在 CKB 部分提到的 Open Transaction,即每个用户可以提部分交易,然后由撮合者(类似定序器),撮合成完整的交易,BTC 与之相对应的解决方案为 PBST。
结尾
通过梳理,了解了 UTXO 的基本原理,知道了其模型与 ETH 的账户/余额模型的优劣之处,并对 UTXO 概念及其相关扩展有了更加清晰的了解。
作为比特币的核心设计原则之一,UTXO 模型在保障交易的安全性和可追溯性方面发挥了重要作用,随着区块链技术的不断发展,UTXO 模型也在不断演化和扩展(如 EUTXO、cell、Strict access list 等),为数字资产的交易和管理提供了更多可能性,通过深入研究和理解 UTXO 概念及其相关扩展,可以更好地把握区块链技术的本质,并为未来的创新和应用打下更加坚实的基石。