作者:0xAyA
作为比特币的核心设计原则之一,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的变化与转移。比如,在上文提到的交易事件中,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在保持其优势的同时有哪些提升点,简单总结为如下几点:
对UTXO所存储状态的意义进行抽象;
对状态的所有权进行抽象。
解决共享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在两个方面有所加强:
eUTXO模型中存在更一般化的地址,这些地址不仅仅可以基于公钥的哈希,还能基于任意逻辑定义在何种条件下可以花费eUTXO,即可以对状态的所属权进行编程。
除了地址和值之外,输出还可以携带(几乎)任意数据,即可以通过脚本对状态的意义进行编程。
具体而言,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,
pub lock: Script,
pub type_: Option,
}
而对于状态争用问题,目前CKB推进研究的是Open Transaction,用户可以提出一个部分UTXO指明交易目的,然后由撮合者撮合成完整的交易。
Nervos的cell模型是UTXO的“一般化”版本,对其详细的科普Jan在Nervos论坛上如此解释:
Layer1的关注点在状态,以Layer1为设计目标的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只能被花费一次。
这样一个模型的特点是:
状态是第一性的;
所有者是状态的属性,每一份状态只有一个所有者;
状态不断的被销毁和创建;
所以说,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概念及其相关扩展,可以更好地把握区块链技术的本质,并为未来的创新和应用打下更加坚实的基石。