
Solana Builders:ZK Compression
在 Helius 和 Light Protocol 宣布其 Solana 零知识(ZK)压缩项目后的几天里,围绕 ZK Compression 出现了大量讨论。其中很大一部分聚焦于命名。它是 ZK rollup?L2?还是完全不同的东西?
为什么这很重要?Solana 生态系统中的一些人认为,围绕命名的争论没有必要。我部分同意:我们如何称呼它,不如它能做什么重要。但名称仍然很重要,因为这些名称指向具有特定属性的架构,并将同类架构归为一组。因此,把它称为 XYZ 可以帮助我们了解它的属性和信任假设,而这些正是我们应该关注的!
属性
在结合 ZK-Compression 评估之前,先列出一些我们关注的 Solana 属性:
- 同步原子可组合性
- 并发性
- 安全性
- 活性
- 抗审查性
初始信任假设
我们将使用“无需信任”一词来描述全节点的安全假设。这一定义是我们的基准。凡是全节点无法独立完成的事情,都涉及额外的信任假设。
全节点是以无需信任的方式与协议交互的唯一途径。如果有人引入了额外的信任假设——无论是跨链桥、委员会、多签、ZK、欺诈证明还是其他机制——却声称它无需信任,那就是错误的。
背景知识
我会用简短的要点快速介绍理解 ZK-Compression 所需的 Solana 背景知识。
- Solana 状态存储在全节点磁盘上的 “AccountsDB” 中
- 存储单元称为“账户”
- 账户拥有地址(每个 32 字节)
- 账户可存储的数据量不等,范围为 0 到 10 MB(上限)
- 在 Solana 上存储 10 MB 数据大约需要花费 70 SOL,由账户创建者支付。这项成本取决于存储量,而不是账户数量——可以是 1 个 10MB 账户,也可以是 1000 个 10KB 账户。
- 当前 Solana 上所有账户的总大小为 76GB(压缩后)
- 每笔 Solana 交易都需要指定它读取和写入的所有账户
- Solana 交易目前的大小上限为 1232 字节(已有提高该上限的提案)
- 每笔 Solana 交易都需要指定一些内容
- 签名(每个 64 字节)
- 账户(每个 32 字节)
- 指令数据(任意长度)
- 近期区块哈希(32 字节)
- 程序地址(每个 32 字节)(CPI:跨程序调用)
- Solana 交易包含一个 32 字节的“近期区块哈希”,交易必须在最近 150 个区块内上链,否则将被视为无效,并需要重新签名和提交
普通交易的生命周期
一笔交易的典型执行生命周期如下:
- 首先对交易执行时效检查(只有近期交易有效)、去重、结构验证、费用(gas)和签名检查
- 根据程序地址加载程序字节码,并实例化 Solana 虚拟机(SVM)
- 检查交易引用的所有账户,将其从存储加载到内存,并传递给 SVM
- 执行程序字节码
- 将所有已修改账户的更新状态同步回存储
ZK-Compression 的主要动机是:
- 链上状态成本高昂。例如,一千个账户需要花费 70 SOL,因此 Drip Haus 这类产品的成本会迅速上升
- 即使不对完整状态进行默克尔化,需要在磁盘上存储的账户越多,快照、索引等也会越大
- 并非所有账户都会被频繁访问,因此没有必要持续消耗资源
那么,实现压缩最简单的方法是什么?
交易不再把账户存储在磁盘上并在需要时读取(交易执行生命周期的第 3 步),而是可以将账户数据作为有效载荷的一部分传入,从而节省链上存储成本。但这会带来一个新问题:如何确保用户没有谎报状态?
例如,假设一个存储代币余额的账户在链下的值为 1200,其所有者字段为“BYixJwV32DjeuyRww72PwZMyKcaedN533GBrv7CDh4n9”。
如果你将包含这些数据的交易提交到链上,链又如何知道你没有谎报地址“BYixJwV32DjeuyRww72PwZMyKcaedN533GBrv7CDh4n9”拥有的代币数量?
毕竟,处理交易的全节点无法访问链下数据——它需要你随交易一起提供这些数据。
你可以使用默克尔证明来解决这个问题。这里不深入介绍默克尔证明,只需将其理解为一种能以很小的链上存储开销,对某些数据作出可验证“承诺”的方法。所有与链同步的全节点都会存储这个小型“承诺”。当有人在交易中提供数据时,也可以在同一笔交易中提供一份能够根据该承诺验证的“证明”。这种证明具有密码学安全性。
这种方式有什么问题吗?
问题在于默克尔证明可能很大。如果一棵树包含 100,000 个账户,其中一个账户的证明大小为 17 * 32 = 544 字节。如果你要为多个账户提供证明,最坏情况下,大小会按证明数量倍增,因此十个账户最多需要 10 * 544 = 5440 字节。这个空间问题是 Solana 特有的,因为 Solana 交易目前限制为 1232 字节,而其他链通常没有这么严格。可参考上文列出的各个组件大小,包括程序、签名、近期区块哈希等。因此,即使在最理想的情况下,仅默克尔证明就会占用一半的交易空间。
这里会引出几个问题。
如果交易大小只有 1232 字节,如何将完整数据作为交易的一部分发送?
这是个很好的问题。如果账户数量非常庞大,但每个账户仅包含少量数据,ZK Compression 就很有用。代币余额(每种代币 8 字节)、NFT 的少量元数据等——100 字节的数据很容易装进一笔交易。考虑到交易还要包含其他内容,1000 字节就很困难。如果你的账户需要存储更多数据,这种方法(以及 ZK Compression)就不适用。
这一说法有一个细微之处:ZK Compression 使用的相同逻辑也可以应用于账户状态的一部分。这意味着,即使无法将账户的完整数据放入单笔交易的有效载荷,仍有变通方式——具体来说,就是对部分数据作出承诺并提供证明。
默克尔证明是实现这一目标的唯一方式吗?
不是。默克尔证明只是向量承诺的一种。其承诺大小为 32 字节,证明大小为 Log2(N) * 32 字节,其中 N 是你所承诺向量的大小。由于 Log2(100000) 为 17,因此得出了前面的 17。不过,也有证明大小恒定的承诺方案(KZG、Pedersen);事实上,ZK-Compression 使用的正是其中一种!
你说,通常作为状态存储在全节点上的账户数据必须随交易一起提供。那么这些数据存储在哪里?
这个问题非常好,也会影响我们的信任假设和系统属性。答案是——任何地方!专用 RPC 服务器可以存储这些数据;它也可以存储在 Filecoin 或 IPFS 中,甚至可以由用户存储在自己的设备上。重要的是,只要向量的所有元素都存储在某处,就可以即时计算证明。我们会在属性部分讨论数据存储位置所带来的影响。
其他链也能这样做吗?
ZK-Compression 特别要求 zk-SNARK 验证成本低廉。因此,它很适合 Solana,因为计算成本低于存储成本。在其他链上,也可以使用向量承诺和证明来验证随每笔交易提供的数据这一通用理念,但由于计算成本高昂,其权衡优势不如在 Solana 上明显。事实上,与一次性的 SSTORE 相比,持续产生的验证 gas 成本在基于 EVM 的链上反而更加昂贵。
什么是 ZK Compression?
- 如果你不了解 ZK Compression,本节会进行介绍
- 如果你只有模糊的概念,我仍建议阅读本节,因为其中存在一些常见误解
- 如果你完全了解它是什么,也请读一遍。如果我有哪里写错了,你可以帮我纠正 :)
如果你理解了上一节,就已经理解了 ZK Compression 的 90%。主要问题在于默克尔证明的大小,因此 ZK Compression 本质上只是使用一种方案来证明某项计算。
如果你不了解 ZK,也没太大关系。你只需要知道,它是一种证明你“正确”完成了某项计算的方法。举个简单的例子:你想证明自己将两个数字相乘得到了第三个数字,即 4*3 = 12
用“ZK 方式”证明这一点,需要使用以下函数:
f(x,y) = x*y
如果你为上述代码生成电路,证明者就能生成计算正确性的证明。电路本身就是承诺,因此所有人都知道你正在运行什么“计算”。妙处在于,他们不需要知道输入。运行 f(3,4) 后,它会返回 12 和“证明”。现在,任何人都可以使用 **12、“证明”**来验证你确实将某两个数字相乘得到了 12。他们不知道你使用的是 4,3、6,2,还是 12,1。你隐藏了这些数字,但其他人仍然能够验证,这就是“零知识”的由来。
为什么我要解释这些?
在证明你完成了某项计算并得到了特定结果时,这一通用理念极其强大。只要获得结果和证明,任何人都能验证你的计算是否正确,而无须实际运行计算。这适用于任意计算。我这里只是将两个数字相乘,但你甚至可以用它来声明:“我验证了这十个签名,它们都有效。”这是第二项优势,也是即使不需要“隐藏”任何信息,人们仍使用零知识证明的主要原因之一。因为你把一个需要运行 1000 个计算步骤(甚至一百万个步骤)的问题,变成了只需验证一份证明,就能确认计算已正确完成的问题。需要注意的是,生成证明需要一些时间。
ZK Compression 使用相同的技术来运行实际的默克尔树成员资格逻辑。它有一个电路,可以接收账户数据和一份证明(128 字节),并验证该数据确实属于链上的“承诺”。(实际证明为 256 字节,但椭圆曲线和点有一个便利之处:如果已知曲线,只需要一个点就能得到第二个点。)
这样做主要是为了将证明大小缩减为恒定的 128 字节,从而仍能为小型账户的数据留下相对充足的空间。普通默克尔证明的大小为 Log2(N),而 ZK Compression 的大小始终恒定,因此单个承诺下可以容纳数量极大的账户。(作为参考,100,000 个账户的默克尔证明约为 550 字节,占交易有效载荷的一半。)
证明可以在链下生成,但必须在链上验证,因为程序需要先确认你为账户提供了正确数据,然后才能继续执行。为此,链上必须具备验证 ZK 证明的基础机制。ZK-Compression 使用的具体证明系统称为 Groth16,它又依赖 alt_bn128 系统调用。该功能目前在主网上受功能开关控制,仍处于测试阶段。
更有意思的是,ZK-Compression 使用的机制可以验证任意计算,而不只是“这个叶节点是否属于具有这个根的树?”。
ZK Compression 的一项主要优势是,它提供了完整的基础设施,让开发者完全不必处理“ZK”部分。从开发者的角度看,他们可以把它当作拥有相同字段等信息的普通账户,因此在程序内部也能作为常规账户处理。将大部分“ZK 魔法”抽象掉,不让开发者直接处理,具有很大价值。
ZK Rollup
不过多展开细节,ZK rollup 使用的理念大多与 ZK-Compression 相同。主要相似之处在于,整个 rollup 状态在基础层(Ethereum)上由单个根表示,因此有人声称 ZK-Compression 是一种 rollup。然而,两者存在关键区别。
我们来考虑 100 笔 rollup 交易。
整个 ZK rollup 被视为一个电路(类似前面用作示例的乘法程序)。系统会验证全部 100 笔交易(签名、合约逻辑、去重检查等),然后生成一份证明,用来证明
“应用 100 笔交易后,状态根从 A 变为 B”。证明通过验证后,智能合约会将状态根从 A 更新为 B。
但在 ZK-Compression 中,这 100 笔交易各自包含一份证明,仅用于说明账户数据正确;而由交易产生的状态转换实际上会作为 SVM 本身的一部分在链上执行。证明通过验证后,该账户会被视为常规账户。这对我们接下来讨论的可组合性至关重要。
重新审视这些属性
现在进入有趣的部分。ZK-Compression 保留了 Solana 的哪些属性?
同步原子可组合性
如果一笔交易引用了 2 个经过 ZK 压缩的账户和 10 个“普通”账户,它不会破坏可组合性。引用 ZK 压缩账户的指令可以调用另一个引用“普通”未压缩账户的指令或程序。即使两个账户压缩在不同的树下,这项能力也会得到完整保留。如果某条指令失败,整笔交易都会回滚(原子性);在第 1 行调用的指令所产生的更改对第 2 行可见(同步性)。
rollup 并非如此,因为 ZK rollup 之间无法同步或原子地相互调用(除非它们获取全局锁并允许跨 rollup 回滚)。
并行性
这项功能会对并行性产生一些影响,值得逐种情况分析:
写入同一棵树下的多个压缩账户
每棵树本身都支持并发。这意味着,如果用户读取或写入同一状态根下的两个压缩账户,这些操作可以并发执行,状态根也可以并发更新。这里的逻辑与 Solana 在 cNFT 中用于并发更新默克尔树的逻辑相同。
写入同一个压缩账户
单个压缩账户不支持并发。如果两个用户尝试写入同一个压缩账户,无论执行顺序如何,其中一笔交易都会失败。在正常执行期间,上一条指令对账户的写入可供下一条指令使用;但对于 ZK 压缩账户,账户数据的证明会失效,因为它证明的是之前的状态。
另一点需要注意的是,压缩会消耗大量计算单元(CU),从而降低每棵树的最大并发度。根据账户 CU 限制,每个账户在每个区块中最多只能使用 12M 个计算单元。
虽然同步原子可组合性是大多数 rollup 面临的问题,但这里强调并行性,主要是为了说明启用 ZK Compression 不需要排序器等额外组件。没有排序器意味着排序由基础链执行。当然,我们也可以讨论 based rollup 如何具备相同属性,但大多数 rollup 并不具备,因为它们使用中心化排序器进行排序。
信任假设
虽然任何人都可以存储生成证明和提交交易所需的全部原始数据,但这会引入额外的信任假设,并影响压缩状态的活性。如果数据因某种原因“丢失”或出现延迟,那么除非你自己存储了数据,否则将无法提交交易。幸运的是,这属于 f+1 问题,而不是需要拜占庭容错的 3f+1 问题。f+1 问题只需要一个诚实节点提供数据,而且由于证明可以“自行验证”,因此不存在“安全性”问题。它主要带来“活性”问题和审查途径。
普通 rollup 和 ZK-Compression 都需要提供有效性证明。但 rollup 会在有效性证明中编码完整的状态转换函数,而 ZK-Compression 只编码“账户数据是否正确?”。因此,这里的信任假设略有不同。在压缩方案中,信任假设主要涉及状态访问,而状态转换会完整执行。在 rollup 中,从基础层的视角来看,信任假设涉及完整的状态转换函数。关于比特数或底层问题的难度与不可解性的安全假设相同(双线性 Diffie-Hellman 假设),不同之处在于,你依赖该安全模型来信任的*对象*不同——状态访问与执行。我之所以提到这一点,是因为我们必须清楚额外的信任假设添加在何处,又没有添加在何处。
目前,用于验证 ZK 压缩账户的程序可以升级,但未来可以将其设为不可变或冻结,因为它只执行高度特定的操作(打开默克尔证明),实际上不需要持续升级。
此外,还可以通过另外两种方式实现状态压缩。
- 提高交易大小的提案(Solana Discord 中的 proj-3x-tx 频道)正在开发中。上线后,如果大小允许,你可以使用普通默克尔证明
- alt_bn128 系统调用上线后,也可以用于证明大小恒定的常规向量承诺(KZG 适用于包括 alt_bn128 在内的任何配对友好曲线)。这种方式不需要 ZK 证明者电路
我们该如何称呼它?
遗憾的是,rollup、L2 和 Validium 等术语的使用非常宽泛,以至于一些所谓的 rollup 甚至根本不是 rollup。它们没有继承基础层的活性、安全性或抗审查性。尽管有人指责 Helius 使用了“营销术语”,但出于同样的原因——营销——这些人也会非常宽泛地使用“rollup”来指称自己投资的项目。事实上,一些并非 rollup 的项目被标记为不同阶段,只为继续自称 rollup。
并非所有人都这样。有些人一直直言不讳地坚持使用准确术语,并花费数月与那些用不准确术语误导用户的人争论(特别致敬 Toghrul,他始终要求*所有人*使用准确术语)。
由于它的某些属性和信任假设与 rollup 不同,将它称为 rollup 可能会让用户感到困惑。称它为 Validium 也过于宽泛,因为这忽略了同步原子可组合性和并行性并未遭到破坏,数据可用性(DA)位于链上,更不用说状态转换函数本身也是无需信任的(因为全节点会完整执行实际程序,而不只是验证针对执行本身的有效性证明)。有人可能会认为 ZK 证明无需信任,但这并不正确。虽然它们极大地降低了信任需求,但从数学角度来看,其安全假设并不相同(它们可能足以满足 99% 的用例,但相较于全节点,仍然施加了额外的信任假设——例如,在基于配对曲线的 zk-SNARK 中,需要依赖双线性 Diffie Hellman 假设)。当然,由于 ZK-Compression 使用 snark 检查账户本身的有效性,因此可以说 ZK-Compression 并非无需信任——ZK 压缩账户存在“普通”账户所没有的信任假设。所以,它处于无需信任与完整 ZK rollup 之间。
如果我们通过名称推断属性,那么我认为,将它称为“rollup”无法体现这些属性或信任假设。或许应该创造一个新名称?只要信任假设足够明确,ZK-Compression 这个名称就很好。
相关文章
订阅 Helius
及时了解 Solana 开发的最新动态,并在我们发布新内容时收到更新


