ZYCORD 文档
简体中文
Zycord文档白皮书

Zycord:一个由自证明状态转换构成的点对点网络

这是一份未经审校的译文

本页由机器翻译,未经审校。凡与英文本有出入之处,以英文本所述的协议为准。 权威的、已存档的正式版本是 doi:10.5281/zenodo.22167490, 英文原文见 zycord.com/docs/whitepaper/。

Zycord:一个由自证明状态转换构成的点对点网络。作者 Simstoshi,v1.0,2026 年。每一笔交易都携带着它读过的状态和它写过的状态,因此核查它只是其字节的一个纯函数 — 不需要磁盘,不需要历史,不需要信任,并行度也没有上限。

摘要

每一条主流区块链的扩容方式,都是让每个节点针对一份共享的全局状态重新执行每一笔交易。吞吐量一涨,节点的配置要求就跟着涨,网络于是走向中心化。我们提出一种由自证明状态转换构成的账本。每一笔交易都携带着它读过的状态和它写过的状态,因此核查它只是其字节的一个纯函数:不需要磁盘,不需要历史,不需要信任,并行度也没有上限。链本身从不执行任何东西。它为凭证排序,并用一个确定性折叠(fold)来提交它们 — 该折叠会应用每一份所声明的输入仍然成立的凭证,并把其余的跳过。跳过不是失败,而是一个被定了价的事件。每一份凭证都由一位承保人承保,由其为该凭证的过时风险担保,于是冲突有了归属者,双签在客观上是可罚没的,而垃圾信息在构造上就是昂贵的。合约按存储槽位逐个声明其访问方式是精确的、带守卫的,还是可交换的;支付、打赏和铸造因此永不争用。手续费拆成两个市场,一个用于串行的状态变更,它是稀缺的,另一个用于并行的验证,它并不稀缺;繁重的密码学因此在设计上就是便宜的。正是这一性质使机密支付成为可能 — 在一张公开的交易图上隐藏金额并使用一次性收款地址 — 其密码学开销完全落在并行市场中,而其通胀风险由一条折叠规则约束在一个屏蔽资金池的余额之内,而不只是事后审计。唯有提交是串行的,而提交不过是一个遍历内存的循环。在发布之初,每个节点都核查全部内容,代价低廉且可并行;一旦验证从逐一重复改为抽样,每节点的成本便会随网络增长而下降。

引用本文。已存档的、带版本的正式记录是 doi:10.5281/zenodo.22167490。本站提供一份 PDF,并附有一份用项目密钥 E724 39CE DD85 11F9 D607 550B 87FD 60D5 EB4A 0B29。下面的正文就是该文档,未经删节。

本文不是什么

白皮书是那份论证。它不是协议本身。凡本文与 规范性表述有出入之处,以规范性表述为准,而这处出入就是一个缺陷: 协议是那些参数文件和黄金向量, 对等层的各项要求则见于 线路规范。解释参考节点如何实现这一切的工程配套文档是 架构。

1. 引言#

今天的区块链节点在做三件毫无共同之处的工作:它散播数据,它验证计算,它变更状态。数据随带宽扩展。验证随核心数扩展:只要交易之间可以独立核查,它就是极易并行的。状态变更是其中唯一真正串行的资源:在某个地方,必须存在一份唯一权威的写入历史。

现有的设计把这三者纠缠在一起。在主流模型中,一个节点不持有全局状态就无法核查一笔交易,于是验证继承了状态的扩展上限;而由于单一的 gas 市场把这三种资源一并定价,一次签名核查便与一次存储写入在同一场拍卖中竞价。晚近的高性能链在节点内部把执行并行化了,但每个节点仍然重新执行所有内容,于是绑定性约束变成了状态 I/O。给出的答案一直是越来越大的机器,也就是说,越来越少的节点。

本文走的是相反的路。我们不去针对共享状态把执行并行化;我们把状态彻底移出并行路径。一笔交易变成一份自带其输入和输出的凭证。核查它只是其字节的一个纯函数。链的职责收缩为给凭证排序,并对它们运行一个确定性折叠(fold)— 一个由比较和加法构成的循环,每份凭证只触碰状态一次。冲突不会让区块失效;它们只会让个别凭证被跳过,而每一次跳过都记在一位有抵押的承保人账上。并发不是在运行时被发现的;它是由合约作者逐个槽位声明出来的。

下面各节依次定义凭证(§2)、折叠(§3)、带类型的状态访问(§4)、承保经济(§5)、应用定序器(§6)、抗审查(§7)、双重手续费市场(§8)、欺诈证明与走向抽样之路(§9)、虚拟机(§10)、无需机器的原生资产(§11)、机密支付(§12)、网络(§13)、三个时代的发布及其国库(§14),以及来自参考实现的测量数据(§15)。

2. 状态转换凭证#

凭证是 Zycord 中唯一的交易类型:

Certificate {
    reads:   [(slot, access, operand)]   // declared inputs
    writes:  [(slot, op, value)]         // declared outputs
    program: bytes                       // code or native-op reference
    sigs:    [signature]                 // spending authority
    underwriter: (id, sig, seq)          // who insures it (§5)
    ttl:     height                      // valid if committed by this height
    fee:     (seq_gas_bid, par_gas_bid)  // two markets (§8)
}

一个槽位是 (address, word)。一份凭证在三项核查都通过时就是有效的,而这三项都不需要任何状态:

  1. 每一个 sig 都为它所花费的那些存储单元授权;
  2. 承保人签名在 (certificate, ttl) 之上是格式良好的;
  3. 针对所声明的 reads 重新执行 program,恰好产出所声明的 writes。签名必须是规范的,而这是一条共识规则,不是某个库的良好教养。一份凭证的 id 是其各授权字段的哈希 — reads、writes、program、承保人、ttl,以及各项手续费出价。签名不在其中,下一段会说明为什么。以该 id 为键的已见集合就是全部的重放防御。手续费是一个授权字段,而不是一项中继偏好,这个区别是货币性的:一个出价是签署者对一笔成本的同意,所以一个落在 id 之外的出价,就是任何一个在传输途中的人都能改写的出价 — 抬高它以通过基础费烧掉发送方的余额,或者归零它以让这份凭证进不了任何区块。一份凭证支付什么,是其签署者所授权内容的一部分,而 id 就是这么说的。规范性所买到的东西比一个 id 更窄,但它同样是一条共识规则:一种容许同一个签名有两种编码的方案,就容许同一份凭证有两个实例字节;而两个对其中哪一个能通过验证意见相左的实现,就已经在一份两者都无法称之为无效的凭证上分叉了。堵上这一点的规则只有一句话:非规范的签名编码是无效的,而一个其验证器会接受此类编码的实现,就不是本协议的一个实现。健全的方案本来就会拒绝这类编码;把它写下来的理由在于,那些不会拒绝的方案也很流行,而一个去采用其中之一的独立实现,会以任何它自己的测试都无法揭示的方式产生分歧。

随机化的证明是例外,而这个例外是结构性的,与编码无关。一个范围证明(§12)是随机化的:对同一个陈述,证明者会选择若干随机数,所以同一个陈述有无穷多个有效的证明,且个个规范,根本不存在什么可拒绝的非规范编码。同一个陈述的两个有效证明并不是同一个证明的两种编码;它们是两个证明,而持有开启信息的收款方随时都能再造出一个新的。规范性因此够不着它们。签名就是其中之一,而这一点容易被忽略,恰恰因为它不属于那些奇特的东西。Ed25519 是一个 Schnorr 知识证明:签署者选择一个随机数,而每一个随机数都会对同一条消息产出一个不同的签名,个个有效,个个完美规范。确定性随机数派生是一条针对签署者的规则,任何验证器都无法核查,任何编码规则也无法强加,因为每一个随机数点都跟其他任何点一样是可规范编码的。于是一次授权就有无穷多个签名,而一个把签名也覆盖进去的 id 就会有无穷多个取值 — 其中每一个都能由该凭证所需签署者中的任意一个造出来,把其余人的签名原封不动地一并带过去,而且每一个都能在它自己的一个区块里被计费。这里的威胁模型比证明的要窄,结论却相同:一个证明可以被一个不持有任何密钥的收款方重新随机化,而第二个签名需要一个该凭证本来就要求的密钥。id 在构造上堵上了这道缝:它承诺的是一份凭证所授权的东西,而绝不是它仅仅展示的东西,所以签名和证明一样,都走在 id 的原像之外。重新签名或重新随机化得到的仍是同一个 id,已见集合会抓住这个副本,而一个同时携带两者的区块是无效的。这就把一份凭证的字段劈成了两类 — 授权,id 覆盖它、签名签署它;以及证据,两者都不覆盖。这一划分并不是一件等着机密支付才登场的 §12 事务;它从区块 0 起就是承重的,在那里一份凭证所携带的唯一证据就是它的签名。验证者在并行阶段从字节里核查证据,然后把它丢掉;只有授权会存续到 id、已见集合和折叠之中。

这一划分制造出一项 id 不再承担的义务,而它在这里被点名,而不是留到生产环境里去发现。落在 id 原像之外的证据,就是任何一个在传输途中的人都能替换掉的证据:拿一份凭证,把它的证明换成垃圾,然后传播出去 — id 不变,实例字节却已无效。两条规则堵上了由此开出的两个洞。第一,区块要对它所携带的证据作出承诺。区块的凭证列表是一张实例字节哈希的清单 — 每份凭证一个叶子,覆盖它的整个编码,证据也包含在内 — 于是「这个区块是有效的」仍旧是一个关于该区块自身钉死的字节的陈述。id 回答的是这次授权是否已被计费,实例字节哈希回答的是这些字节能否证明它,而这两个问题从不共用一个键。只要证据与编码不可分离 — 在证据就是签名的时期正是如此 — 一个叶子就够了;当 §12 的证明把它变成一个独立字段时,叶子就变成(id,证据哈希)这一对,而承诺还是同一个承诺。它绝不能变成的是一张 id 清单:那样一来就等于对任何具体的证据都不作承诺,而既然凭证列表的根是一个头部字段、头部又是工作量证明的原像,那么替换证据就将毫无代价,而那份工作量却照旧成立。这是见证承诺式交易的先例,而这里遵循它的理由正是它当初被发明的理由:一个覆盖证据的 id 是可锻的,而一个对未被承诺的证据视而不见的 id 是盲的。第二,中继处理的是实例字节,而不是 id:一个节点收到某个实例字节、其证据验证不通过时,就丢弃那个实例字节,而不牵连该 id — 该 id 不被标记,不被缓存为无效,之后一个能通过验证的实例字节仍会正常中继。一份被篡改的副本让篡改者付出发送它的带宽,而让该凭证分文不损。提议者的盲性完好无损:它打包的是它自己那套无状态核查所接受的实例字节,所以它既不可能被诱导去打包一个被篡改的证明,也不可能被诱导去打包一个坏签名,而 §3 的那条断言也保持着它原有的含义。

有效性是凭证字节的一个纯函数。任何机器都能核查它:在一个线程池里,在另一台计算机上,在一块 GPU 上,不需要数据库,不需要历史,也不需要同步。签名核查可以跨凭证批量进行;来自同一个程序的凭证可以批成单指令多线程的工作负载(§6);范围证明(§12)批量验证时边际成本是对数级的。这就是本文其余一切所建立于其上的那条性质。

有效并不足以让它被执行。两份有效的凭证可能为同一个槽位声明同一个输入值;其中至多有一份能生效。因此我们把传统上的交易有效性概念一分为二:有效(无状态、可并行、任何人都能核查)和可应用(有状态、串行、只由在账本中的位置来裁定)。下一节定义第二个谓词。

3. 作为一次折叠的账本#

一个区块包含一个头部、一张有序的凭证哈希列表,以及凭证体本身。凭证体是链数据:一个区块只有在其凭证体可获取时才有效(§13)。一个区块的提议者不执行任何东西,也不持有任何应用状态;它只是给一些字节排序,而这些字节的有效性它单凭字节本身就能核查。它并非完全无状态,而这个例外值得点名而不是四舍五入掉:它必须持有 TTL 窗口内已见过的凭证 id 集合,因为把同一份打包两次会让区块无效,而没有人能被意外诱导至此。那个集合的大小由 TTL 界定,且可剪除,这正是 TTL 是一个共识参数而不是一项中继偏好的原因。提议是刻意做得廉价而愚钝的,不是刻意做成盲的。

链的状态被定义为对有序凭证的一次折叠(fold):

apply_block(S, B):
    assert bodies_available(B)
    assert ∀c: height ≤ c.ttl ∧ ¬seen(c.id)     // an expired or replayed certificate
                                                 // makes the BLOCK invalid: a signature
                                                 // is billable at most once, and never at
                                                 // a position its signer could not avoid
    C ← sort(B.certs, key = (underwriter.id, underwriter.seq, c.id))
    for c in C:
        if leased(S, c):     bill(c, LEASED); mark_seen(c.id, c.ttl); continue  // §7; Era 1 only
        ok ← true
        for (slot, access, x) in c.reads:
            EXACT:  ok ← ok ∧ (S[slot] = x)
            GUARD:  ok ← ok ∧ pred_x(S[slot])
        if ¬ok:              bill(c, STALE);  mark_seen(c.id, c.ttl); continue
        w ← stage(S, c.writes)   // a target whose address is spent, or a delta that would
                                 // over- or underflow, fails HERE — nothing has landed yet
        if w = ⊥:            bill(c, STALE);  mark_seen(c.id, c.ttl); continue
        commit(S, w); settle_fees(c); mark_seen(c.id, c.ttl)

写入是要核查的,而且在其中任何一项落地之前就核查。本节草图的早先版本无条件地施加写入集,读起来就好像一笔贷记能让一个已花费的存储单元复活、又好像一个增量能回绕。两者都不成立,而两者都会是致命的,所以那个暂存步骤如今就写在草图里,而不是留给规范去说。算术在它出现的每一处都要核查:一个会进位越过 2²⁵⁶ 或跌破零的增量,会让该凭证被跳过而不是回绕,这也正是为什么 §11 的供给上限在无限并发的铸造之下依然成立。一次写向权限已被烧毁的地址的写入会大声失败,而不是无声消失 — 那个永久性的已花费注册表就是为此而存在,它也是 §5 归属定理中那唯一一个例外的来源。

**leased 是 Era 1 的机制,创世时并不存在。**这里之所以把它展示出来,是因为本节所规定的是整个协议的折叠;但发布时代没有租约、没有强制队列,也没有需要用它们来防护的联署方,所以这个分支在创世二进制文件中是不可达的 — 而不可达的共识代码就是无法审计的共识代码,因此它不随之发布。有一个尚未解决的问题在此被标出而不是被搪塞过去:照现在的写法,LEASED 会在一个其签署者无从预见的位置上给一份凭证计费,而这恰恰是上面第四行那条断言所禁止的。要么这个结果不得计费,要么它必须让区块无效。这个选择属于引入租约的那个时代,之所以记在这里,是为了让那个时代无法悄无声息地把这处矛盾继承下去。

有几条性质值得明确说出来:

跳过是语义,不是失败。一份其读取不再成立的凭证会被跳过,它的跳过费记在它的承保人账上(§5),而该区块仍然有效。打包与应用是两个不同的事件。正是这一点让提议者可以是盲的:它绝不可能因为打包了一份过时的凭证而产出一个无效区块。

确定性。每个节点都从同一段区块序列导出同一个状态。排序键 (underwriter, seq, certificate id) 是区块内容之上的一个全序,所以折叠对提议者如何交错来自不同承保人的凭证是不敏感的 — 而且它根本没给提议者留下任何自由裁量的余地,哪怕是在同一位承保人以同一个 seq 签署的两份凭证之间也没有。一位承保人自己的流水线(seq 递增)会按它被签署的顺序提交,无论提议者是以什么顺序提交它的。

每份凭证恰好触碰状态一次。折叠在一个热的键值工作集上执行比较、相加和写入:不执行代码,不验证签名,不重新执行。所有这些都已经发生过了,在并行中,在有效性核查的时候。

也没有椭圆曲线运算。机密支付(§12)把 Pedersen 承诺放进槽位值里,而一个承诺是一个曲线点。该方案是同态的,两个承诺之和是有意义的,这就诱使折叠去把它们相加。折叠拒绝。一次点加法要花几百纳秒,而一次 u256 加法只要约 1 纳秒;在这唯一的串行阶段里放进一次椭圆曲线运算,就会耗尽这套架构所要守护的那份预算。折叠只把承诺字节存入新鲜的存储单元,并比较它们是否相等 — 两者都是内存操作;所有曲线运算都发生在并行阶段,或者在持有者的钱包里(§12)。同样的纪律也管着下面要说的哈希。

有一件密码学工作并没有被移除,而声称移除了未免太过便利:一份凭证的 id 是其各授权字段的哈希(§2),而已见集合以该 id 为键,所以总得有人去算它。要紧的是在哪里算,而答案是不必在这里。id 只取决于凭证的字节而不取决于别的任何东西 — 不取决于状态,不取决于顺序,不取决于位置 — 所以它可以在并行阶段与签名核查一同算出,然后像凭证本身一样被带进折叠。一个改在串行循环里重算它的实现,会发现哈希主宰了一个本该只关乎内存的阶段,而这个错误值得点名,因为它是一个很容易犯的错误。我们希望对这里究竟移除了什么、没移除什么说得精确。折叠仍然会在完整状态上做随机访问,每声明一个槽位就触碰一次,而一个负责提交的节点仍然持有那份状态。离开串行路径的,是在一条常规链上环绕着那次访问的一切 — 解释执行、签名核查、哈希、对一个 Merkle 化存储的逐操作认证 — 于是状态可以住在一张扁平表里,而这个系统那唯一的串行阶段就成了一个紧凑的内存循环。参考实现的折叠在一台十核 x86 台式机上每核每秒持续完成 883,000 次槽位操作(§15),而它是唯一一个不能并行的阶段。

是值相等,不是版本号。可应用性比较的是所声明的读取值,而不是版本计数器。因为执行是读取集的一个纯函数,一个变过又变回来的槽位(即 ABA 情形)仍然是可应用的:现在应用这份凭证,在语义上与当时就执行它完全相同。因此该系统的跳过严格地少于基于版本的并发控制。对承诺值的槽位而言,这个比较就是承诺的字节相等:不透明、精确,而且同样廉价 — 前提是 §4 的那些规范性条件得到强制执行。

重放与单次计费。一份凭证的 id 是它所授权内容的哈希,而绝不是那些用来展示该授权的签名的哈希(§2),而已应用的和已计费跳过的凭证一样都会被标记为已见。正是这一区别让这条规则成其为规则:一个用新的随机数把同一个体重新签名的签署者,产出的是同一个 id,于是已见集合会抓住这个副本而不是给它计费。打包一份已见过或已过期的凭证会让区块无效 — 因此一个签名至多只能被计费一次,而且只能在其签署者通过签名所接受的位置上被计费。没有这条规则,一个区块生产者就能重复打包或蓄意拖延别人的凭证,以烧掉他们承保人的资金。TTL 由共识界定,所以已见集合始终可剪除。

4. 带类型的状态访问#

一个只支持精确读取的折叠会把每一个热门合约都串行化:一千份触碰同一个 AMM 池的凭证只会得到一次应用和 999 次跳过。Zycord 的答案是,并发由合约作者逐个槽位、在凭证格式本身之中声明出来。共有三种访问纪律:

精确。SLOAD 式:读取把槽位的值送入计算,并把凭证钉死在这个值上。可以承载任意逻辑;会在热门槽位上发生冲突;是应用定序器的用武之地(§6)。

带守卫增量。凭证对一个槽位断言一个谓词 — balance ≥ 10 — 而不把该值读入计算,并写入一个带符号的增量 — balance += −10。折叠拿这个谓词去核对当前状态,然后施加该增量。对同一个槽位的任意多次带守卫的借记和贷记都是可交换的:它们以任何顺序施加都行,而只有在某个守卫真正不成立时(一次真正的透支)才会发生跳过,而不会仅仅因为余额变了就跳过。这一条纪律就覆盖了转账、有供给上限的铸造、授权额度和金库份额,也就是说,覆盖了链上写入中的绝大多数。

纯增量。一个不带守卫的带符号增量。它永远不会冲突,也永远不会跳过。打赏、计数器、累加器、奖励统计。

让这个模型保持健全的那条规则是:被守卫的值不得流入计算。一个断言不返回任何东西;一个增量不读取任何东西。因此重新执行仍旧是所声明读取的一个纯函数,而无状态的有效性(§2)得以保全。这个想法的血统既老且牢靠:数据库中的托管事务、事务提升、CRDT [9][10][11]。但现有的链至多只是在一个节点的调度器内部利用可交换性;Zycord 把它变成节点之间的并发契约,由凭证格式强制执行,由承保经济定价。

还有一条同等分量、由 §12 所迫的规则:任何持有隐藏值的槽位都不接受第三方守卫。一个其结果公开可观察的守卫 — 应用了还是跳过了 — 若施加于一个本应保密的余额,那它就是一台余额预言机:提交 balance ≥ x,看着折叠,二分,log₂(balance) 次尝试就能读出这个数,而根本不必开启那个承诺。修补是结构性的,不是统计性的。隐藏值只住在一次性存储单元里,只能凭其持有者的签名花费;而带守卫增量这套纪律,其全部要点在于让陌生人可以安全地触碰一个槽位,因此它只留给那些取值是公开的槽位。拉取式支付 — 授权额度、订阅、金库归集 — 因此只存在于透明轨道上(§12)。

要强制执行那条规则,就要求折叠能够分辨一个隐藏槽位和一个公开槽位,而在一张扁平表里它无法凭观察做到:一个压缩承诺和一个 u256 余额都是 32 字节。因此一个隐藏值存储单元并不是一个恰好装着一个承诺的普通存储单元;它是一个独立的存储单元种类,只由原生的屏蔽操作(§12)创建,并居住在地址空间中一个保留的、可派生的区域里,从而使一个槽位的纪律成为其地址的函数,并且像其他一切一样可以从字节里核查出来。若没有这一点,一个 — 因失误或因恶意 — 瞄准某个承诺槽位的带守卫增量,就会让折叠把一个 u256 加到一个曲线点编码上:算术核查通过了,结果却不是一个承诺,而那个存储单元变得无法花费。无声地毁掉价值、而折叠中却没有任何东西失败,恰恰是 §3 的暂存步骤所要防止的,而存储单元种类就是让它也能防住这一种情形的东西。

另有两个原语补全了这个模型:

一次写入存储单元。一个从未在链上出现过的地址处于状态 ∅;第一次写入把它移到已存;一个来自其密钥的签名把它移到已花费,永久地。往一个新鲜的存储单元里写入,按定义就是无冲突的,所以向一个新派生出来的地址付款就是那条零争用的快速路径。这就是这个网络的嵌合血统 [8]:账户式的持久存储单元用于合约,UTXO 式的一次性存储单元用于支付,同处一个账本。一个已花费存储单元之下的那些值,在花费它的那个区块被埋到比重组视界更深之后就可以压实。这里说的是确认深度,而不是某个终局性小工具,因为发布时代还没有终局性可等。那条把该地址记录为已花费的注册表条目则永不压实:正是它阻止了该地址被复活,而它就是这个协议诚实承认的尚未解决的问题,与每一种作废符集合设计共有。§12 的隐形输出原封不动地走这条轨道,而它让注册表增长的速率,恰好就是透明支付本来就有的速率:每花费一次一个条目,隐藏与否皆然。

零就是不存在。一个从未被写过的槽位和一个被写成零的槽位是同一个槽位:写零会删除该存储单元,而读取一个不存在的存储单元得到零。这不是实现上图省事,而是一项共识要求,而且它双重承重。正是它让一个守卫或一次精确读取可以指名 0,而不必去问那个槽位是否存在 — 根本没有第三种答案需要消歧。也正是它让状态根保持为状态的函数,而不是产生出该状态的那段历史的函数:假如一个被清空的存储单元作为一个显式的零滞留下来,那么两个经由不同路径达到相同余额的节点就会承诺出不同的根,而那就是一次以记账方式到来的链分裂。

这一点约束了 §12,且带有一处锋利的边角。一个 Pedersen 承诺 vG + rH 一般来说不是零串,所以一个隐藏余额不会因算术而变成不存在,而除了持有者之外没人分辨得出它已经空了;持久的隐藏槽位将永远无法压实,这又是屏蔽轨道只用一次性存储单元的一条理由 — 它们因被花费而死去,这是一种「零就是不存在」从来不必提供的转换。那处边角在于,v = 0, r = 0 是群的单位元,而在 Ristretto 编码里单位元就是三十二个零字节 — 恰恰就是那个意味着不存在的串。一个承诺绝不能与删除相撞,所以单位元不是一个有效的承诺:屏蔽操作在入口处就拒绝它,同时也拒绝一个曲线点本来就需要的那些规范编码核查(非规范的域元素、扭曲分量),而参考的 H 是一个 NUMS 点,其派生过程已公开。“字节相等即精确”(§3)只有在这些条件成立之后,才是一个关于点的断言。

纪元信标。程序不得直接读取环境值(时间戳、高度);那会让执行成为所声明读取之外的某种东西的函数。取而代之的是,协议每个纪元往一个保留槽位里写一次纪元信标,程序像读取任何其他槽位一样读取它,最好带上一个范围守卫(epoch ∈ [e, e+2]),这就在没有逐块过时的前提下提供了时间感知。

5. 有承保的凭证:每一次冲突都有归属者#

跳过绝不能是免费的,否则内存池会被淹没:一个攻击者可以针对同一个输入发布成千上万份有效凭证,把区块塞满,却只为其中一份付费。但被跳过的凭证从未碰过发送方的余额,所以没有什么可以向发送方收取;那个手续费槽位恰恰就是那个过时的输入。Zycord 的答案是:没有承保人就没有凭证 — 承保人是一方以其抵押资金为该凭证负责的当事人。

被承保有三种方式,实为一套机制的三副面孔:

  1. 自保。发送方从一个未被占用的存储单元附上一小笔保证金。事先不租用任何东西:折叠在该凭证于区块中的位置上预留这笔保证金(§3 的草图略去了这套管道),而一份到达应用阶段时保证金已被消耗的凭证会被丢弃 — 不计费,不标记为已见,可以针对一笔新鲜的保证金自由重新提交 — 所以诚实的用户不会因为自己那个保证金存储单元上的竞争而损失任何东西。一次保证金完好时的跳过会烧掉其中一部分。相比之下,发布本身分文不费,所以内存池是由中继策略而不是由共识来界定的:节点为每位承保人设定它们愿意持有的上限,这与自比特币以来的每一个内存池是同样的分工。这就是那个无需许可的底线:任何人随时都能在没有对手方的情况下交易。它也是发布时代(§14)里唯一的模式,这让创世保持最小。那个保证金存储单元是公开的,而且是发送方的,这让自保成为一个钉在它所签署的每一份凭证上的持久标识符。对透明支付而言,这并未泄露该支付本来就没泄露的任何东西;但对一笔屏蔽支付(§12)而言,它会把这笔支付所换来的那个隐形输出给毁掉,而 §12 转向联署,恰恰是出于这个理由。
  2. 联署。一位有抵押的承保人拿这份凭证去核对新鲜状态,联署 (certificate, ttl, seq),并承担跳过责任。作为交换,它可以在链外向发送方收费,而它的联署本身就是一件产品:一份由承保人自己的资本作后盾的预确认。这个角色里没有任何一处需要看见一份凭证在转移什么:过时是存储单元的性质 — 活着还是已花费 — 而不是金额的性质,所以一位承保人为一份屏蔽凭证定价的方式,与它为一份透明凭证定价的方式相同,而不必被托付以那个数值。

有两样东西不能让那个承诺把它们搅混,因为本文以前就搅混过。协议对一次跳过的回应是从承保人的保证金中烧掉那笔跳过费 — 这是一笔罚金,不付给任何人,正是它让任何人都无法靠制造跳过牟利。补偿发送方则是另一回事:那是承保人的商业承诺,而本设计尚未把它规定为一种共识机制。要把它变成共识机制,不是措辞问题:它需要一个在联署中声明的保单额度,需要一个不是发送方本人的具名受益人(否则这件产品就是在邀请保险欺诈),还需要在链上追踪总敞口,以免一份抵押被卖两次。在这些东西存在之前,“我的抵押赔付”是一位承保人所作的一个声明、一个市场为之定价的东西,而不是折叠所强制执行的一条规则,而本文就这么说,而不是让这处含混去替产品卖货。

  1. 强制。那条抗审查路径(§7),由一笔用户保证金承保,并且独此一家地保证会被应用。经济激励在两类不端行为之间是不对称的,而这个不对称正是要点所在:

客观过错会被罚没。如果一位承保人联署了两份其精确读取相互冲突的凭证(同一个槽位、同一个所声明的值、TTL 有重叠),这两个签名就是一份自足的、链上的双签证据;任何人都可以提交它们,而那份抵押会被罚没。同样,一位承保人若联署了一份通不过无状态有效性的凭证(一个坏签名、一次错误的执行),只需纯粹的重新执行就会被罚没:那份凭证就是那份欺诈证明(§9)。

主观过错永不罚没。两位不同的承保人在同一个槽位上竞速,并未犯下任何可证明的过错;在折叠顺序中靠后的那一位吃下一笔小额跳过费,仅此而已。迟缓的、离线的或不可靠的承保人失去的是资格和声誉,不是资金。当延迟变成没收,网络就会死掉;Zycord 只没收那些能从字节里被证明的东西。

而账单落在一个被该凭证指名的当事方头上。本节的标题是一条定理而不是一句口号,而它值得连同它的边角一并说出来。一次被计费的跳过必定恰好是以下三者之一:那个不成立的读取或写入位于某个其密钥签署了该凭证的地址之下;或者它是一次铸造,与同一资产所声明的铸造者发起的另一次铸造相竞速,而那位铸造者也签了名;或者它是向某个一次性地址的一笔贷记,而该地址自己的持有者在该凭证被签署之后把它退役了(RETIRE,§11)。任何不被该凭证指名的当事方都无法导致它被计费。只有第三种情形把账单与起因分了开来,而它在构造上是有界的:只有一次性存储单元才可能被退役,所以一个公布持久地址的收款方根本不暴露任何面;一份凭证至多只能退役固定数量的地址(§13),这就限住了一阵退役潮所能触及的在途支付有多少;而且跳过费是被烧掉而不是被支付的,所以没有人能靠触发它牟利。

抵押规模遵循一条不等式:一位承保人的抵押必须超过它在一个 TTL 窗口内所能累积的最大跳过责任,而解除抵押所需的时间必须长于欺诈证明窗口,这样就没有人能作恶、提款、然后人间蒸发。

6. 应用定序器#

带守卫增量化解了支付的争用。剩下的是热状态之上的精确读取逻辑 — 一个订单簿、一个 AMM 池 — 在那里两位诚实的承保人竞速仍然会产出跳过。协议的答案是让合约挑选它自己的串行化者:一个合约可以在链上注册一位有抵押的定序器的密钥,此后只有经该定序器联署的凭证才可以走该合约的精确读取路径。

定序器是一台由应用团队运行的普通服务器 — websocket、队列、自动扩缩,任何 web2 机制皆可 — 而它只在活性上、绝不在安全性上被信任。它无法伪造状态:它若在构造凭证时对输入撒谎,该凭证在权威折叠面前不过是被跳过,而这个谎言花掉的是它自己的抵押。它所提供的是:

串行化。它把槽位以短 TTL 租给在途的凭证,并把每一份凭证链接在前一份所声明的输出之上(它用 seq 给自己的联署编号;折叠的全序保证它的流水线按序提交,无论提议者如何洗牌)。热槽位争用于是变成一个链下的调度问题,对链而言不可见。

批量凭证。走同一条代码路径的 N 笔交易折叠(fold)进一份凭证,其读取/写入被聚合,内部的中间写入被抵消掉,而 N 次子执行会作为单个 SIMT 工作负载被验证。GPU 论点正是在这里落到实处:负责串行化的那个实体,同时也是把工作打包成并行硬件所要的形状的那个实体,并且通过并行 gas 市场为此获得报酬(§8)。

原子可组合性。一笔横跨两个应用的交易由两个定序器在链下共同构造(一次基于槽位租约的两阶段提交),并作为一份同时带有两个联署的凭证落到链上,在构造上就是原子的。跨应用协调发生在协调廉价的地方;链只看到结果。

诚实的披露:一个定序器最先看到它那个应用的订单流,因此它是该应用 MEV 的天然场所。Zycord 把这笔交换摆到明面上:应用捕获自己的 MEV,而不是把它泄漏给区块提议者,而 §7 限住了这意味着的那份权力。同一条保证退出的强制路径也约束着这种榨取:一个不喜欢某定序器待遇的用户,可以彻底绕开它,代价仅仅是延迟。

7. 强制打包并保证应用#

一个唯一的门就是它那个定序器的合约,一旦定序器审查起来,它就是一座牢笼。因此每个用户都有一条不需要任何人许可的慢速路径:

一个用户(或任何中继者)从公开的折叠重建状态,构造一份凭证,附上一笔保证金,并把它提交到强制队列。提议者必须在 F 个区块之内打包排队的凭证(§13)。在它被打包的那个位置上,折叠拿它的读取去核对当前状态;若成立,折叠就在它的那些槽位上授予一份确定性租约,并把应用安排在 D 个区块之后。在租约期内,触碰那些槽位的联署凭证一律排在它之后。因此这份强制凭证不可能被跳过:被准入即意味着被应用。

宽限期 D 是留给诚实定序器那条在途流水线的 — 确切说是其中不触碰被租槽位的那部分。凡是确实触碰它们的,无论联署还是自保,都排在这份强制凭证之后,而正是这一点让「不可能被跳过」成为事实而不是愿望:一份其读取在整个宽限期内都受保护的凭证,在此期间不可能变得过时。因为写入是声明出来的,所以应用之后的状态是可预测的,于是一个定序器可以有把握地在一份尚未被应用的强制凭证之上继续链接。

一份租约只可覆盖该凭证对其拥有权限的槽位。这是一条该机制并不白送、而设计的其余部分离了它就活不下去的约束。声明一次读取不需要任何签名 — 只有写入才要导出授权 — 所以没有这条规则的话,任何人都可以排入一份声明了跨越他人槽位之读取的强制凭证,以一笔保证金的代价把它们冻结 D 个区块。每合约的配额也界不住它,因为原生支付不属于任何合约。因此一份租约只有覆盖该凭证有权写入的槽位时才是可准入的,而这与折叠本来就在施加的是同一项权限检验,只是提早了一步使用。

另有一些反骚扰的界限:每合约、每纪元的配额;一笔覆盖所强加成本的保证金;已被租用的槽位在释放之前拒绝更多强制条目。

有两项后果把这从一个特性抬升为一种生存机制。第一,审查被反转了:它让审查者付出代价(丢掉的手续费、绕开它的用户),而让网络分文不损。第二,一个定序器消失了的合约 — 或者一条整个承保人阶层遭到攻击的链 — 会退化为仅强制模式:慢,贵,但活着。没有任何状态会被遗弃,也没有任何运营者是安全性的承重件。对一个被设计来比它的创始者活得更久的网络而言,这就是那条要紧的性质。

8. 两个市场:串行 gas 与并行 gas#

一条链消耗着三种可扩展性等级不同的资源 — 数据、验证、变更 — 而把它们放在一个市场里定价,就意味着一次零知识证明的验证会与一次存储写入同台竞价。Zycord 把手续费拆成两个独立的市场:

串行 gas 为折叠操作定价:读取核查、写入、租约。它是那种稀缺资源,是每个节点都要按序跑的那唯一一个循环,因此它被相应地定价。

并行 gas 为验证定价:签名核查、重新执行的计量单位、字节数,以及繁重的预编译(证明验证、签名聚合、后量子方案)。它是充裕的,其供给随着网络里每增加一个核心和一块 GPU 而增长,而它的区块上限被设得又高又便宜。

两个市场都是 EIP-1559 的形状 [15],各以自己的单位计:基础费被烧掉,优先费付给该区块的生产者 — 只在被应用的凭证上,且仅限于此。一份被跳过的凭证,其手续费被全额烧掉,不付给任何人,而这不是费用表里的一个细节,而是整个经济模型的承重规则。假如跳过也能付小费,那么决定一个区块装什么的那一方,就会因为安排别人的失败而获得报酬,而最便宜的赚钱方式就会是养跳过而不是打包工作。烧掉把它反转了过来:一次跳过既占用了上限空间又什么都不产出,所以一个要把收入最大化的生产者就是在把应用最大化,而且它不只是对制造跳过无所谓,而是积极地反对它。烧掉串行基础费,让那唯一一个稀缺循环中的拥堵具有通缩性(§14.2)。烧掉并行基础费,则让提议者对是哪些繁重凭证填满这条便宜车道无所谓,于是验证车道的优先权无法被账外出售。这一拆分沿着多维手续费设计的方向 [18],并把它一路贯彻到彻底分离的市场。两个市场都不决定的是那种稀缺资源究竟应该有多少;那是一个上限,而 §8.1 给出了移动它的规则。

其后果就是那条被表述为不变式的设计目标:繁重的密码学不冲击网络,这是经济构造使然。一份验证了一个大证明却只写两个槽位的凭证,几乎全部的开销都付在那个便宜的市场里。Zycord 由此把自己定位为那个结算层:那些对单一市场的链而言过于繁重的密码学协议,在这里是划算的。同一个市场也为定序器的塑形工作付费:一个定序器把 N 笔交易聚合进一份批量凭证,为它付一份并行出价,然后向它的用户收 N 份的钱 — 这个差价就是把工作做成 SIMT 形状的报酬(§6)。

8.1 弹性串行上限#

一个手续费市场为稀缺定价;它并不决定应该有多少稀缺。此前对这个问题的两个答案都失败了,且失败的方向相反。一个固定的上限把采用变成一场用户注定输掉的拍卖:当比特币的区块被填满时,手续费越过了 50 美元,普通支付就这样被价格挤出了链,唯一的受益者是卖出那份空间的人。一个没有需求托底的敞开上限则从另一头失败:它那些大区块分叉买来了无人使用的容量,并用安全性付了账,因为手续费来自成交量,而不是来自空间。因此 Zycord 的上限既不固定也不由投票决定 — 一个其作者不会在场为之分叉的网络,不能把例行的增长当成一次治理事件,而一次投票就是一个把手:矿工从限制供给中获利,大型运营者则从把供给扩张到小节点跟不上的程度中获利。这个上限是一个关于所测得需求的共识函数,从区块 0 起就在创世里,谁也动不了它。

这条规则有三个部分,每个时间尺度一个。在一个区块之内,每个市场都有一个目标 T 和一个硬性的弹性界 2T;基础费每个区块按一个有界的比例向均衡迈进,形状即 §8 的 EIP-1559 — 之所以有界,是因为无界的调整已知会振荡而不是收敛。跨纪元,串行目标跟随需求:T ← clamp(2·median_applied(e), T − T/Δ, T + T/Γ),下限锁在它的创世值 — 其中 median_applied 只统计被应用的凭证上的串行 gas。被跳过的凭证烧掉自己的手续费而不留下任何登记,所以抬高上限的唯一办法就是赢得应用:持续的、不冲突的、烧掉基础费的使用。强行催生长,对一个攻击者的花费恰好等于有机采用对其他所有人的花费,也就是说,这个机制分不出两者,而它也不需要分。增长还进一步被一个纪元健康信号所把关 — 观察到的竞争性头部的出现率保持在一个阈值之下(Era 0),检查点按时终局(Era 1 及以后)— 从而使容量绝不会跑到传播已被证明能承载的程度之前。一个陈旧区块恰恰是链不会记录的那种事件,所以这个信号只能以唯一诚实的方式被引入:头部可以引用近期的竞争性头部,无偿地;引用需要真实的工作量证明,而压制这个信号则需要几乎每一个提议者都省略掉引用,而任何单独一个诚实的提议者就能把它恢复。这种不对称让这道关卡倾向于谨慎,而那正是一道关卡应该倾斜的方向。逐块地,还有一个爆发阀:一个提议者可以超出 2T,最高到 4T,并按二次方罚没该区块贷记给它的东西 — 它那份补贴份额加上该区块的手续费 — 对照 §14.2 的时间表计算,作为一笔永久性的短缺,不重新分配给任何人 — 而罚金以串行 gas 总量计,被应用的和被跳过的一并计入,从而使超出的部分无法用人为制造的冲突来打折塞满。之所以以生产者的收入而不仅仅是其补贴为基数,是因为两者并不同步衰减:补贴在 §14.2 的曲线上会跌到其创世值的约 1.6%,而一次爆发所要买到的手续费收入却完全不跌,所以一个只以补贴计价的威慑,恰恰在机会最大的地方停止了威慑。一项常设的性质必须以某种跟踪着其收益的东西来定价,而这正是 EIP-1559 为把一笔收费与操纵所赚到的价值挂钩而提出的论点。在创世时这几乎不改变什么,因为手续费接近于零。§14.1 的国库份额取自未经削减的补贴,且永不被罚没,因为爆发是生产者自己的选择,而国库不是其中一方。真实的激增立刻买到通行,并把信息喂给纪元控制器;垃圾流量买到的是一笔罚金。先例是门罗币的罚金-中位数机制 [19],其被记录在案的停滞 — 当典型的工作单位逼近中位数时增长就冻结 — 在这里被构造性地避开了,因为批量凭证(§6)让典型单位相对于目标始终很小,而速率界限则沿袭了自适应上限的血统 [20]。并行上限则不需要这些讲究:它被钉为串行目标的一个固定的高倍数,并继承其增长,这就是 §8 那条不变式以容量的形式被重述了一遍。

从这个机制里读出那场拔河。用户的手续费向下限靠拢,因为持续的拥堵按定义就是那个抬高上限、进而把手续费压回去的信号 — 比特币那种永久拍卖的均衡在这里是达不到的。生产者在网络年轻时靠补贴获得报酬,在成熟时靠成交量,而绝不靠稀缺;那笔烧掉让每一次拥堵事件都累积到生产者所持有的那个币上,而 §8 已经让应用而不是排斥成为收入最大化的策略。有一处诚实要说两遍:弹性所保证的仅仅是容量绝不会成为采用停滞的理由 — 它并不制造需求,大区块分叉已经证明了这一点 — 而一个会增长的上限也让状态得以增长,这正是为什么新鲜槽位的写入在串行 gas 中的定价高于增量:折叠所居住的那张扁平表,其膨胀速度不会快于喂养它的那个上限。由此得出一条校准规则,之所以要说出来是因为违反它是无声的:串行目标与字节上限的创世比值,必须低于这个网络所要承载的那种流量的密度,从而使一个装满普通支付的区块抬高基础费而不是压低它。一个对自己的设计负载给出反向反应的市场什么也定不了价,而这个比值是在冻结之前依据实测流量设定的。

跨时代的容量。凭证数量上限和字节上限是随串行目标一同缩放的,而不是固定地立在它旁边:在创世时这三者彼此相差不到一倍,所以一个固定的上限在翻一番之后就成了真正的限制,并让那个有弹性的上限沦为摆设。它们背后立着一些静态容量:那个决定区块 merkle 深度的凭证列表宽度,以及一个区块所能达到的字节容量,还有其下的传输常数与之配套 — 一个区块是分块传送的,所以没有哪一条网络消息会界住一个区块。列表宽度在创世时就按整条曲线来设定(2²⁵ 份凭证;日后重新钉定它会让同一条链上出现两种 merkle 宽度,而虚拟填充让这份余量是免费的)。字节容量按发布网络来设定,并在时代边界上重新钉定 — 反正那里已经是硬分叉(§14)— 依据健康关卡所测得的传播情况;绝不在一个时代之内,也绝不通过投票。这架梯子所服务的那条曲线由 Γ 得出:目标每满一年的健康满块最多翻一番,所以创世上限的每秒约 90 份被应用的凭证(每 30 秒间隔一个 §15 的区块),在持续需求下七年后达到每秒约 11,000 份,十年后约 90,000 份,而不到十四年就达到 2²⁵ 的列表宽度所固定的每秒约 110 万份 — 那是唯一一堵没有哪个时代能挪动的墙,因而是这架梯子的尽头而不是它上面的一级。这里的单位是凭证,不是支付:一份凭证携带一笔支付,或者携带一个定序器批进去的 N 笔(§6),所以每一个数字都是交易量的下限,而不是关于交易量的断言。条件恰好是三条,每一条上文都已是一个机制:需求必须持续把区块填满,因为被应用的 gas 是唯一能抬高 T 的输入;传播必须让健康关卡保持开启,因为它关闭的那个纪元增长就被扣住;而那些重新钉定必须落在各个时代上,因为在它们之间字节容量就是那堵墙。计算不是一个条件 — 折叠在单个实测核心上就跑完了这条曲线的远端(§15),而验证随着这个网络并不拥有的硬件一同扩展(§2)。带宽是:凭证体是链数据(§13),而抽样(§9)分摊的是验证,绝不是数据,所以每个节点都要承载每一个字节,而一个处在曲线顶端的生产者仅就带宽而言就是数据中心级的 — 这对生产者是一个可接受的终态,对其他任何人则不是,因为验证的每节点成本是朝相反方向走的(§9)。而在带宽成为约束之前,状态先成为约束:已花费注册表(§4)每花费一次一次性存储单元就增长一个条目,在每秒 10⁵ 份凭证时约为每年 10² TB,这就把 §4 所声明的那个尚未解决的问题从一个常设条目变成了这条曲线排上日程的工作。

常数(创世,示意性的,同 §13):增长除数 Γ = 512(每纪元;上限每满一年的满块最多翻一番);衰减除数 Δ = 1024(闲置容量约两年减半,绝不低于创世值);爆发界 4T,到界时罚没生产者的补贴份额以及该区块的手续费(§14.1 的国库份额取自未经削减的补贴,且永不被罚没,因为爆发是生产者自己的选择,而国库不是其中一方);健康关卡:每纪元被引用的竞争性头部 ≤ 区块数的 2%;凭证列表容量 2²⁵(结构性的,如上);字节上限创世时 2.5 MB,随目标缩放至 8 MB 的结构性字节容量(在各时代重新钉定);每区块签名上限创世时 6,000,随目标缩放,在验证任何签名之前就先核查 — 这是对验证工作量的一个界限,与并行 gas 的价格分开保持,因为一个参数不能既界住工作量又给市场定价。

9. 即时欺诈证明与走向抽样之路#

因为有效性是一份凭证字节的纯函数,所以一份无效的凭证就是它自己的欺诈证明。任何重新执行它的人都能用一条消息立刻驳倒它,不需要状态,也不需要交互式的二分博弈。在发布配置下,这一点以最好的方式变得无关紧要:每个节点在接受一个区块之前都会核查每一份凭证,所以一份无效的凭证根本活不到需要被质疑的时候。这个窗口要等到验证被抽样之后才有意义,而到那时它是一个传播参数而不是一套争议协议:给一条消息几个区块的时间穿过网络,费用记在那位为该凭证背书的承保人账上(§5)。对比一下其他方案:乐观 rollup 需要长达一周的交互式争议,因为提出争议需要争议步骤处的状态;基于证明的系统避开了争议,却要为那些比执行贵上几个数量级的证明者付钱。Zycord 的欺诈证明花的就是验证的价钱:约 1 倍的重新执行。

这条路是重新执行式的链走不了的扩容之路。原则上,这个网络并不需要每个节点永远核查每一份凭证。有了承保人抵押在位,验证就可以被抽样:为每份凭证选出一个 VRF 委员会,其规模使得一份未经核查的无效凭证出现的概率可以忽略不计,同时任何全节点都可以自由核查任何东西,而一条消息就足以触发罚没。在创世时,人人核查一切;那既便宜又可并行。抽样属于路线图,不属于发布。但这正是那条把整个行业的曲线反转过来的路线图:每节点的成本随网络增长而下降,而重新执行范式的每节点成本却随吞吐量增长,直到只剩下数据中心。

10. 机器:cEVM#

Zycord 只跑一个虚拟机:cEVM,它是以太坊 EVM [2] 的一个方言,为凭证作了适配。这个选择是刻意的。这个项目的新颖度预算已经花在了状态模型、并发模型和经济模型上;机器则应该是这个系统里最眼熟的东西。Solidity 及其编译器、审计员和工具链都可以照搬过来。

与标准 EVM 的差异:

  • SLOAD 编译为一次精确读取(在凭证中声明);SSTORE 编译为一次 SET 写入。
  • 新增操作码 SASSERT(slot, pred) 和 SDELTA(slot, ±v),用以暴露带守卫增量和纯增量。一次 SASSERT 不往栈上推任何东西 — 守卫不返回值(§4)。
  • TIMESTAMP 和 NUMBER 读取纪元信标槽位;除此之外没有别的环境输入。
  • gas 计量是双重的(§8):存储类操作码计量串行 gas;计算、calldata 和预编译计量并行 gas。
  • 交易格式就是凭证。以太坊合约在源码层面可以移植;我们不宣称原生的钱包兼容性,也不假装有。一个被移植过来的合约以全精确模式运行:第一天就是正确的,若它变热就通过一个定序器串行化。并行性是按槽位选择加入的,通常只是一个小改动(一个代币的余额映射从 SLOAD/SSTORE 改成 SASSERT/SDELTA,约 30 行)。为了让这个旗舰特性从虚拟机时代的第一个区块起就看得见,而不必等移植,这台机器会与一个原生标准库一同激活 — 代币、打赏罐、托管、归属,以及一个订单簿/AMM 混合参考实现 — 全部以增量优先的方式写成,并预先部署在已知地址上。

11. 无需机器的资产#

发布时代需要一个经济体,早于它需要一台计算机。因此 Zycord 从区块 0 起就交付作为凭证操作的原生资产,不牵涉任何虚拟机:ISSUE(在一个一次写入存储单元中创建一个带供给上限的资产 id)、MINT(带守卫:minted + Δ ≤ cap)、TRANSFER(守卫 balance ≥ Δ,配对的增量;一笔瞄准某个纯增量或某个新鲜一次性存储单元的贷记就是那笔打赏,所以打赏并不占用它自己的操作码),以及 RETIRE(烧掉一个地址而不花费它:没有读取,没有价值转移,只是一次把该存储单元标记为已花费的纯写入 — 真正的花费在 TRANSFER 内部是自动的)。RETIRE 双重地挣得了它的槽位。它是那个用于压实和隐私的原语,让一个收款方在一个一次性地址服务完毕的那一刻就把它抹掉;它也是 §5 归属定理中那唯一一个例外背后的操作 — 第三种情形之所以存在,正因为退役存在,这就是为什么凭证格式要限制每份凭证可退役的地址数(§13),也是为什么那条定理要连同那处边角一并陈述而不是绕开它。这个集合是封闭的:这四个就是全部的创世指令集,其他每一个操作 — 包括抵押 — 都随某个后来时代的规则集到来(§14),而 §5 的归属定理正是针对这个界面被证明的。

一个在一个区块里收到一万笔打赏的主播,给折叠带来的成本是一万次可交换的加法:零冲突,零跳过,没有定序器,没有机器。早先那些链上的打赏文化寄居在平台 API 的恩准之下,也随之而亡;在这里,打赏是一个协议原语。而且它并非顺带地,还是对这个系统核心主张的一场持续的压力测试。原生币的供给上限就住在这里,在透明轨道上,由 §3 的核查算术强制执行,而 §12 对它们分毫未动。

12. 机密支付#

前面各节都把一笔支付的金额当作公开的。本节加入把它藏起来、并把收款人藏在一个一次性地址背后的选项,同时保全前面各节的那些性质。这个设计比它所源出的那些系统更窄:下面每一条限制,都堵上了一个更通用的设计将不得不用更重的机制去应对的攻击。

什么被藏起来了,什么没有。一笔屏蔽支付藏起它的金额,并推迟它与收款人之间的关联。要对「推迟」这个词说准确:一个新鲜的隐形输出在它被收到的那一刻是不可关联的,但由于没有环签名,日后花费它就会指名被花费的那个输出,而指名那个输出就暴露了是先前哪一笔支付为它注了资。不可关联性维持到第一次花费为止,并在那里终结;它是一次延迟,不是一次抹除。这个设计也不隐藏发送方在图中的位置:凭证是被签署、被承保、被公开排序的,所以一个观察者能看到一笔支付发生了、以及是谁承保的 — 看不到金额,而且在某次花费披露之前,也看不到收款方的哪一个输出去了哪里。诚实的描述是在一张公开图上、带一次性收款地址的机密交易,而这张图会随着输出被花费而追溯性地去匿名化。发送方一侧的模糊性 — 环签名、对历史输出的成员资格证明 — 不在范围之内:一个环要引用别人的输出,这会让有效性成为历史的函数,而 §2 的无状态性正是这个协议不肯放弃的那条性质。门罗币的威胁模型需要一个不同的协议。

屏蔽输出。一笔屏蔽支付写入一个新鲜的一次写入存储单元(§4),其地址是一个隐形地址:发送方从收款人已公布的查看密钥和它自己的一个临时密钥派生出一个一次性地址,只有收款人认得出它,也只有收款人的花费密钥能为它签名。该存储单元的值是一个 Pedersen 承诺 C = vG + rH;与它同行的还有一个证明 v ∈ [0, 2⁶⁴) 的范围证明,以及用收款人的查看密钥加密的 (v, r) 对,外加一个一字节的查看标签,让扫描中的钱包可以尽早丢弃绝大多数与己无关的输出。所用的原语是机密交易加隐形地址:RingCT 中隐藏金额的那一半,去掉了环,两者都有既成的生产部署。

有效性依然是字节的函数。一份屏蔽凭证的余额核查就是那个承诺等式:所声明的输入承诺减去所声明的输出承诺等于 fee·G,其中手续费是公开的(见下文的资金池)。这个等式、那些范围证明和那些签名,全都仅凭凭证的字节就能核查,在并行阶段,批量地:Bulletproof 的验证在一个批次上以对数方式摊薄,而这一切都不触碰状态。一份伪造的通胀凭证 — 其承诺在某个被攻破的假设或某个被攻破的验证器之下并不平衡 — 仍然纯粹凭它的字节就是有效或无效的,而折叠从不重新核查它;这是 §2 的有效/可应用划分在按设计工作,而不是它的一个例外,而下文的资金池规则才是那个让一次健全性攻破不至于无界的东西。这里也正是双重手续费市场(§8)从一个效率论证变成一个使能论证的地方:在一条单一 gas 市场的链上,机密交易之所以昂贵,是因为一毫秒的证明验证要与一次存储写入同台竞价,而在这里验证完全落在 par_gas 里 — 设计上就便宜 — 串行市场则根本看不见它。

折叠存的是字节。在应用阶段,一笔屏蔽支付是折叠所见到的最便宜的凭证:它的输出存储单元是新鲜的,所以它不可能冲突(§4 那条零争用的快速路径);它的输入存储单元是活着还是已花费,一次注册表查询即可;而那次写入存的是 32 字节的承诺。没有任何曲线运算进入串行阶段(§3)。这条规则所排除掉的那个替代方案,是一个由陌生人以同态方式贷记的持久「累积槽位」,它在三条战线上都不成立:它把椭圆曲线加法放进了串行循环(§3);它制造出一个被重复使用的公开标识符,而那正是隐形地址要避开的共同所有权启发式;而一个隐藏的持久余额又会招来 §4 所禁止的第三方守卫。累积发生在钱包里:一个收款人持有许多一次性输出,全都能通过同一个查看密钥认出来,并通过把自己的若干存储单元花进一个新鲜存储单元来把它们合并 — 这就是一份普通的屏蔽凭证,节奏由它自己定,用纪元信标(§4)当钟。一个持久槽位本会在链上维护的那个总和,改由持有者在链下维护 — 而合并也不是不留痕迹的:在一份凭证里声明多个输入是一个公开的共同所有权事件,比一个被重复使用的标识符弱,但确实存在,所以钱包的策略是尽量少做,并且绝不在一次合并里混入来源无关的输出。

只有持有者能花费,以及这禁止了什么。一个隐藏值只能凭其持有者对它自己那个存储单元的签名来花费。针对隐藏余额不存在第三方守卫(§4),因此屏蔽轨道上没有拉取式支付:没有授权额度,没有订阅,没有金库去归集一个用户的屏蔽资金。这些模式仍旧留在透明轨道上,而两条轨道之间的边界只有一份凭证那么宽。这条限制消灭了那台余额预言机:既然没有陌生人能提交一个猜测去撞一个余额、再从跳过里读出结果,这台预言机就没有操作员了。

屏蔽资金池是一个由折叠强制执行的公开整数。手续费是公开的。coinbase 是公开的。付给合约槽位的款项是公开的。透明轨道与屏蔽轨道之间的每一次穿越都会移动一个公开可见的 v — 一份屏蔽凭证在入场时公开 v,一份解屏蔽凭证在离场时公开 v。因此资金池总额住在一个保留槽位里,以明文形式,只由带守卫增量来移动(§4):屏蔽为它贷记,pool += v;解屏蔽守卫 pool ≥ v 并为它借记,pool += −v;一份屏蔽凭证的手续费同样为它借记。这个槽位恰好就是 Σ 入 − Σ 出,而带守卫增量这套纪律让无限多次穿越可以在无争用的情况下交换顺序。

这让资金池成为一道栅栏,而不是一个警报器。隐藏金额是拿 §3 那种带核查的 u256 算术,去换一个离散对数假设的计算健全性,而一次攻破 — 攻破那个假设,或者远为可能地攻破一个验证器 — 会在池内伪造出承诺。但一个伪造的承诺不携带任何明文,而价值离开资金池只有一条路,就是解屏蔽,而解屏蔽要在它的守卫之下为那个公开槽位借记。这个槽位只因真实的屏蔽而上升过,所以一次会把它压到零以下的解屏蔽会被跳过:伪造出来的价值过不了那道守卫。通胀并不会把资金池抽干;它是出不去。一次密码学失效的杀伤半径是有界的,由一条折叠规则而不是由一位审计员的警觉来划界,界限就是资金池的真实内容;而失效的表现形式不是一次无声的抽干,而是一场可见的、实时的竞速,其中最后那批诚实的持有者解屏蔽不出来 — 糟糕,但有界,而且可观察,这正是一个没有应急响应团队的系统必须在构造上就具备的那种遏制能力。先例是具体的:Zcash Sprout 的伪造漏洞之所以能挺过去,是因为它的屏蔽资金池是一个有界、可审计的量;而在这里,那个量不只是可审计的,它在折叠中是承重的。§11 的原生供给上限住在透明轨道上,仍旧由带核查的算术强制执行,分毫未动。

资金池有一项隐私成本,而它该被记录在案:边界穿越会暴露精确的公开金额,而独特的金额会被关联起来。一个观察者,若看到 v 在 v + fee 进入资金池后不久离开它,就学到了任何承诺都没能藏住的某样东西。在边界上采用标准面额可以钝化这一点,而钱包默认这么做;协议并不强制要求,因为一条共识规则分辨不出一个独特的金额和一个正当的金额。流量分析下的可关联性在这里被连同它的缓解措施一并陈述,而不是被否认。

承保人就是元数据,而隐私与抗审查在同一份凭证里不能共存。一份自保的屏蔽凭证会指名发送方那个公开的保证金存储单元,从而把隐形地址所解开的关联又重新接上(§5);因此屏蔽流量走的是联署承保,在那里一位承保人的 id 汇聚了许多发送方,人群就是掩护。承保人为存储单元的活性定价,而不是为数值定价(§5),所以它并不获知金额 — 但它获知这份凭证的其他一切:它必须在链外向之收费因而必须识别出来的那个发送方、正在被花费的那些存储单元、正在被创建的那些隐形输出,以及时间。而那恰恰是一张传票所要的东西,再配上上文那种追溯性去匿名化,它就会沿着那张公开图向前传导。它也是一个结构性的 KYC 点,因为那唯一的私密模式有一个可知的操作员。这暴露出一处局限,而本文把它直说出来而不是搪塞过去:两种承保模式是联署的(私密,但需要许可 — 一位承保人可以拒绝你)和自保的或强制的(无需许可,但会自我标识)。§7 的强制路径保证一个被审查的用户永远可以交易;它并不保证他们可以私密地交易。一个被每一位承保人都拒绝的用户,保住了花费的权利,却在同一个动作里失去了隐私 — 而那恰恰就是隐私对之最要紧的那个用户。屏蔽轨道要继承 §7 的抗审查性,代价只能是它自己的隐私;这两条性质在单独一份凭证里并不同时成立。

是一个时代,不是创世。有两条相互独立的论证敲定了这个时间安排。在实践中,屏蔽凭证使用的是联署承保人,而后者自 Era 1 起才存在。而按照 §3 的原则,不可达的共识代码就是无法审计的共识代码,因而不随之发布:创世二进制文件里没有承诺,没有范围证明验证器,没有资金池槽位 — 凡是其唯一调用者位于某个未来时代的东西,一概没有。屏蔽时代并不是纯粹增量式的,而本文就这么说:§4 的隐藏存储单元种类划分和第三方守卫禁令,触及的是折叠的守卫路径,那是创世关键界面,所以这个时代既是增补也是修改,而它的激活是一次硬分叉,要按它本来就是的那种共识关键变更来审计。一个证明不了自己值这份复杂度的时代,干脆就永远不被触发,而创世的那条链并没有因为不包含它而失去任何东西。

成本,直说。一份屏蔽凭证的大小是一份透明凭证的 3–5 倍 — 一到两千字节,即便经过批量聚合,也仍以范围证明为主 — 动态区块(§8)在经济上把它吸收掉,而 §13 的散播则以带宽为它付账。收款人靠扫描来发现付款:每一个新的屏蔽输出都要拿钱包的查看密钥去试探一遍,这是一项随网络成交量呈 O(n) 的成本,查看标签靠一个字节上的提前拒绝把它降下来,那是一个由先于它的共享密钥派生所界定的常数因子。扫描是屏蔽轨道主要的用户体验负担;一种不必交出查看密钥的外包扫描,是这个设计所继承而非解决的一个尚未解决的问题。中继策略在这条轨道上也必须改变,而且没得选:发布一份凭证是免费的(§5、§13),而验证一个 Bulletproof 不是,所以一个在向发布者施加任何成本之前就跑验证器的中继,就是一台拒绝服务放大器 — 屏蔽轨道要求先廉价后昂贵的中继核查(先签名和所声明的活性,证明放最后,每承保人配额),这就把内存池那句「由中继策略界定」从一个默认值变成了一项要求。

本节留给后续草案的一些尚未解决的问题,在此标出而不是埋掉。屏蔽轨道究竟只承载原生币,还是也承载 §11 的资产:单一生成元 H 使 vG + rH 承诺的是一个标量,而不是一个(资产,数值)对,所以一条多资产的屏蔽轨道需要按资产设置生成元(机密资产),这会改变证明大小,也会改变上文那个「3–5 倍」的数字;在定下来之前,这条轨道按规则只支持原生币,而不是因为疏漏。RETIRE(§11)究竟对屏蔽存储单元起不起作用:如果起,价值就能不经一次公开的解屏蔽而离开资金池,于是那个资金池槽位就变成一个上界(Σ 入 − Σ 出 ≥ 内容),而通胀检测的方向也必须重新表述为一个不等式;如果不起,屏蔽轨道就失去了它的垃圾回收器,而那些无法开启的输出(见下)就会堆积起来。加密后的 (v, r) 究竟住在凭证体里(眼下便宜,但一旦凭证体被剪除就无法恢复,从而使仅凭种子恢复钱包落空),还是住在存储单元的值里(每个输出都要花状态,存储单元被花费时随之消失,并且与「累积发生在钱包里」相互吻合)。以及一位承保人能否在为第三方作保的同时、在凭证内部以公开数值收费,那将免去上文那项身份要求,也是绕过隐私/审查那处局限最有希望的路线 — 这些是设计问题而不是措辞问题,之所以在此点名,是为了让下一版草案无法悄无声息地把它们继承下去。

13. 网络#

凭证体是链数据。一个区块的凭证体必须可被取回,该区块才是有效的;因此状态永远可以仅凭链本身重建,而没有任何运营者 — 包括定序器 — 会成为数据托管人。凭证比传统交易更大(它们携带读取和写入),而缓解措施是结构性的:批量凭证抵消掉内部写入(§6),已花费的一次性存储单元在被埋过重组视界之后就可以压实(§4)。

还有第三种缓解措施是前瞻性的,并且就以这个名义提出:一次读取可以用 (cert_id, index) 去引用某份先前凭证的写入,而不必重复那个值 — 也就是 UTXO 的那个技巧。它不属于这里所描述的协议,而且在一个问题有答案之前它也不会成为协议的一部分,因为这个问题是一个共识问题而不是一个编码问题:当被引用的那份凭证被跳过了、或者压根没被打包时,一次引用究竟解析成什么。一次无声地解析为当前值的引用,就不再是一次所声明的读取,而无状态的有效性也随之而亡;而一次会失败的引用,则会拖垮那些自身授权从未受过质疑的凭证。以整个设计所倚赖的那条性质为代价的压缩,就不是值得要的压缩,所以在语义定下来之前这个字段是缺席的。

中继是哈希优先的。凭证各自独立地扩散,并在到达时被核查有效性(无状态地、并行地);区块以头部加哈希列表的形式对着内存池中继,即紧凑区块的做法 [13],所以传播延迟并不随区块内容而增长。一个中继节点完全不需要任何状态就能彻底过滤垃圾信息 — 无状态有效性加上承保人签名都能从字节里核查出来,这让在透明轨道上运行中继基础设施几乎是免费的。屏蔽轨道(§12)是那个有价格的例外:它的无状态核查里含有一次范围证明验证,其代价比一次签名高三个数量级,所以在那里,发布免费这个默认设定就成了一台放大器,于是中继要受先廉价后昂贵这条纪律以及 §12 把它从一项偏好变成一项要求的每承保人配额所约束。一条轨道的中继几乎是免费的;另一条的中继之所以便宜,仅仅因为它的策略是强制性的。

参数(创世,示意性的)。30 秒出块;凭证 TTL 默认 240 个区块(约 2 小时);每份凭证退役的地址数 ≤ 64;纪元 2,880 个区块(约 1 天);强制应用延迟 D = 4 个区块;强制打包界限 F = 16 个区块;时代触发的质押窗口 K = 14 个纪元(约 2 周);国库份额为区块补贴的 300 个基点(3%),自区块 0 起计,存储单元封存至 Era 2,此后由硬分叉钉定的一套密钥集之上的 5 选 3 掌管,密钥轮换延迟 R = 20,160 个区块(约 7 天)(§14.1);发行常数见 §14.2。

14. 发布:三个时代,零预挖#

Zycord 发布时没有预挖,没有创始人份额,没有投资人轮次,也没有管理员密钥。创世可以从已公布的源码复现。先跑一个 testnet;只有当它稳定之后,才会公开地、提前地宣布 mainnet 日期,以便任何想要区块 0 的人都够得着它。对作者所强制的是特权的缺席,而这在创世里可以逐行核查。升级通过社会共识和硬分叉发生。

本文是那份论证。规则是架构规范,黄金向量才是协议,而其中任何两者有出入之处,以更精确的那个为准,而这处出入就是一个缺陷。与它们一同公布的,还有这个设计被攻击的记录 — 一次接一次的对抗性阅读、它们所产出的发现、其中改变了规则的那些,以及被驳倒的那些和为什么被驳倒。那份记录完整且未经编辑地发布,包括那些找出了真实缺陷的评审,以及那些明明什么都没测到却报告成功的仪器。对于一个其作者不会亲自出面为之背书的项目,一个看不出被攻击历史的设计,读起来就像是一个没人攻击过的设计,而藏起它的代价是无论如何都赚不回来的。

「未经编辑」是一个关于那些发现的承诺,而它究竟涵盖什么值得说准确,因为这份记录是作为文件而不是作为一个在线归档重新发布的。每一项发现、每一次测量、每一段论证和每一个被否决的替代方案,都以它当初被写下的样子存续下来,包括那些错了的,以及那些明明什么都没测到却报告成功的仪器;没有任何东西被稀释、被软化、被合并或被丢弃。只有两类改动被作出,也只有两类。第一类是身份脱敏 — 姓名、账号、地址、机器名和本地路径,它们对这个设计什么也没说。第二类是自足性:一个指向所发布的文件树并不携带的某个跟踪器或某段提交历史的指针,会被它所代表的那段推理所替换,就地写出。因此一个只持有这些文件的读者,仍能跟上每一项主张,而这正是这个承诺的用意所在;一个解析不到任何东西的指针,会保住「未经编辑」的字面,却丢掉它的全部目的。

权益证明无法做到公平发布 — 最初的权益必须从某个地方来,而每一条经典路线(出售、预挖、份额分配)要么让网络集中,要么让它的作者暴露身份。因此工作量证明在这里被用作一种有到期日的分发机制,而不是一种永久的共识:

时代触发条件共识存在什么
0 — 挖矿与打赏区块 0(公开的 mainnet 日期,在一个稳定的 testnet 之后宣布)PoW(RandomX [14]),中本聪规则 [1]原生资产(§11);每一份凭证都是自保的;没有租约,也没有强制队列 — 两者都是 Era 1 时代的机制(§3、§7);国库计入,封存(§14.1)
1 — 支付高度 H₁(指令集);一旦抵押质押量 ≥ 供给量的 1% 并持续 K 个纪元,终局性覆盖层启用PoW 负责提议;自覆盖层激活起,有抵押的验证者每 32 个区块终局化一次检查点(FFG 覆盖层 [12]),补贴按 77/20/3 拆分 — 生产者 / 检查点见证者 / 国库BOND、承保人与定序器(§5–6);cEVM 加标准库(§10);预确认市场随终局性一同成熟(设计说明)
2 — 平台高度 H₂ 并且质押量 ≥ 10% 并持续 30 个纪元PoS 委员会负责提议;PoW 奖励在约 90 天内斜降至零,检查点无法终局化期间暂停斜降;随机性从 PoW 哈希转为 VRF完整协议;国库存储单元在 5 选 3 之下打开(§14.1);抽样研究(§9)启动
S — 屏蔽 (针对当时正在运行的那个时代激活;需要 Era 1 的指令集)由节点运营者采纳的硬分叉,只有在下列各项全部满足后才可提出:联署承保已上线(§5);验证器及其批量路径已公布,并附至少两份独立审计,发现与整改公开;完整的屏蔽轨道已在一个有对抗性参与的公共 testnet 上运行 ≥ K 个纪元;验证器的黄金向量已在协议制品中不变 — 这个时代不增加任何共识角色,也不改变任何排序规则机密支付(§12):屏蔽操作、隐藏存储单元种类与守卫禁令(§4)、资金池槽位及其折叠规则、作为共识关键代码的范围证明验证器

关于这些转换的设计说明。Era 0 是刻意做得极小的。既然没有管理员密钥,就没有暂停按钮,所以创世里的每一行都是一行可能在无从补救的情况下杀死网络的代码;折叠(§3)加上原生操作(§11)就是全部的安全关键界面(小到本文可以完整地把它们规定下来),而它们在发布时已经过审计和对抗性模拟。CPU 挖矿(RandomX)与目标人群相匹配:用你手头已有的那台笔记本挖矿。它也会招来僵尸网络;每一种可用 CPU 挖的币都跟它们斗过,我们也不指望自己是例外。我们睁着眼睛接受这笔交换:一份被僵尸网络扭曲的分发,仍旧比一份在一场出售中被决定的分发更广,而让诚实的商用硬件保持竞争力正是 RandomX 的全部设计宗旨 [14]。联署自承保人能够注册的那一刻起就上线;检查点终局性则不然,要等质押量持续达标,而我们把这段空档点名而不是把它藏起来:抵押为跳过定价,绝不为重组定价,所以没有任何协议规则能阻止一位承保人在这段空档里卖出预确认 — 而没有哪条规则能做到的,是让这笔买卖诚实。「几秒内应用,否则我的抵押赔付」只有在深度重组被排除之后才承保得起,所以那件有承保的预确认产品是作为市场诚信而不是作为协议律条跟在终局性之后的。见证者自覆盖层激活的那一刻起就从发行中获得报酬 — 此前的两次混合转换都是这么做的(Decred 把每个区块奖励的一份付给 PoS 投票者 [17];以太坊的信标验证者在提议 mainnet 区块之前已经赚了两年的发行)— 而那个偏向生产者的 77/20/3 拆分只限于分发阶段,因为同样的先例表明,偏向矿工的拆分是一个错误的永久状态:那道斜坡会让它退场。Era 1 把它的触发条件拆成两半,而这一拆是承重的:指令集按高度激活 — 先是 BOND,之后才是承保人与定序器注册以及 cEVM,这是创世参数保持区分且有序的两个高度 — 而终局性覆盖层只有在抵押质押量 ≥ 供给量的 1% 已持续 K 个纪元之后才激活,因为在创造质押的那个操作存在之前,质押量根本无从测量。Era 2 保留一个双重触发(高度并且持续的质押量),从而使网络既不会在质押量微不足道时就转换,也不会让在位的矿工永远拖住它;高度是阈值,不是日期,而且没有承诺任何日程表。有一处需要说明而不是埋掉的注意事项:在 1% 这个激活下限上,拖住终局性只需要其中的三分之一(供给量的 0.33%),所以头几批有承保的预确认所倚赖的那份终局性本身还很年轻。K 个纪元的持续要求会随时间抬高门槛,不活跃惩罚让一次拖延维持起来很昂贵,而承保人也被预期会把年轻的终局性计入年轻的保单价格;更深的安全性随更深的质押而来,不靠宣告。Era 1 是那条影子链:验证者集合在整个时代里都在生产环境中做终局化 — 真实的抵押,真实的罚没 — 然后才提议第一个区块,而这正是让那唯一一次大规模 PoW→PoS 转换得以安全完成的那条性质。它也是一个静止态,不是一条走廊:如果质押量始终达不到 Era 2 的阈值,网络就无限期地保持为一个运转着的混合体。用一道奖励斜坡而不是一道悬崖,就否掉了一次矿工分叉集结所需的那个时机,而这道斜坡是可中止的:如果检查点连续两个纪元无法终局化,它就在当前水平上暂停,直到终局性恢复。矿工买不到这次暂停:拖住终局性要占抵押质押量的三分之一,而不活跃惩罚会一直放它的血直到终局性回来,所以这次攻击的代价高于那份被暂停的奖励所能付的。把自己的 coinbase 抵押出去的 Era 0 矿工会在最初几轮定序器轮换中获得优先权 — 不是额外的币 — 从而把挖矿社群转化为运营者社群,而不是把它丢掉。

屏蔽时代的触发条件被刻意设计成与其他各时代不同的形状。高度和质押阈值是节点可以测量的事实;一个密码学验证器的就绪程度则不是,所以这里的触发条件改成了一份硬分叉必须能够援引的清单:审计已公布,testnet 纪元已服役,向量已在制品中。形式比条目更重要。这个网络没有应急响应团队 — Zcash 那个伪造漏洞之所以能挺过去,一部分原因是一个有编制的组织在几天内交付了修复,而这里没有任何东西能作此承诺 — 所以这个时代的激活被设计成去创造它的维护者,而不是假定他们已经存在:一个拿不出两份独立审计、也配不齐一个对抗性 testnet 的社群,就是一个维护不了一个共识验证器的社群,而这份清单让第二种无能以第一种无能的失败形式显现出来,在激活之前而不是在攻破之后。§12 的折叠规则界住了一个活下来的漏洞能造成多大代价;这份清单界住的是一个验证器在开始造成任何代价的那一刻可以有多未经审视。两者谁也代替不了谁,而这个时代要么带着两者一起发布,要么根本不发布。

这道斜坡是一次重新划分,而不是一次削减:工作量证明让出多少份额,权益证明的提议者就拿走多少,所以 §14.2 的补贴时间表不受网络处在转换的哪个位置所影响。正是这一点让这份时间表保持为高度的一个纯函数,任何节点都能仅凭四个常数算出来,而不必查阅账本。

这次转换之所以可行,有一个值得说出来的架构上的理由:折叠对谁来排序是不可知的。一个 PoW 矿工本来就是这个设计所要求的那种盲提议者;Era 2 改变的是头部上是谁的签名,而不是状态语义的任何一条规则。这里没有什么执行引擎需要跨过这道边界。

一个时代边界也是容量移动的地方:§8.1 的字节容量以及其下的传输常数都在那里重新钉定,依据在运行中的网络上测得的传播情况,从而使例行的增长搭上这份时间表本来就包含的那些升级,而不是自己变成一次治理事件。

14.1 国库#

每一笔区块补贴的百分之三计入国库存储单元;百分之九十七付给共识 — 该区块的生产者,以及自 Era 1 起的检查点见证者(§14)。这个份额固定在创世里,自区块 0 起适用,并对每一个区块一视同仁地适用。手续费从不被动(国库对 §8 的那两个市场没有任何主张权),所以这份成本落在发行上,按持有比例分摊到每一位持有者身上。

这个存储单元封存至 Era 2。它自区块 0 起计入,而在那之前没有任何密钥能打开它:创世里没有法定人数,没有地址,没有作者密钥,也没有应急通道。在区块 0 时,还不存在一个成熟到可以持有这些密钥的社群。如果始终没有出现,这个存储单元就永远不会被打开,这些币也就永远不会被发行。那条计入规则自第一个区块起就坐在创世里,正是为了让它永远不成为一次改动;到 Era 2 时并不会发生什么新东西,只不过一条已经约定好的规则开始生效而已。

这一切都不是预挖,而这个区别是可核查的,而不是修辞性的。一次预挖是一次份额分配:在创世时就存在并且归属于某人的币、地址和密钥。这里的创世不含这三者中的任何一个。没有任何国库币是可花费的,没有指定任何地址,不存在任何密钥,也没有指名任何一方;创世所包含的是一条规则,对有史以来被挖出的每一个区块一视同仁地适用,外加第二条关于某个法定人数日后可以如何在累积结果之上被钉定的规则。那一天会不会到来,不由作者说了算。因为密钥集是由硬分叉钉定的,所以选择机制就是每一次共识变更所用的那一套:有人提出五位公开具名的持有者,节点运营者要么采纳钉定他们的那个分叉,要么拒绝运行它,而一个说服不了网络去运行它那个分叉的候选法定人数,手里的密钥什么也打不开。这里没有可被俘获的投票,没有可被钻空子的贡献者名册,也没有哪一刻作者的声音会比任何其他节点运营者的更有分量。

自 Era 2 起,这个存储单元只由一份携带某个由硬分叉钉定的密钥集之上五个签名中的三个的凭证来借记。支出都是普通凭证:公开、最终,并且仅凭链就能清点。法定人数从开启之时活跃的贡献者中选出,并在该密钥集生效之前公开具名;每一把密钥都由一个不同的当事方持有,没有任何两把处于共同控制之下。一次有效的 5 选 3 支出也可以改为指名五把替换密钥,在 R 个区块之后生效,在此期间这次轮换是公开的,而旧的密钥集仍然有效:轮换承担着一次支出的门槛,也承担着一次支出的可见性。

Era 0 和 Era 1 由捐赠资助:提案在付款之前公开,里程碑与预算先行给出,按已交付的工作拨付,未花完的部分退回资金池。存储单元开启之后,法定人数继承这套纪律。它是一项规范而不是一条共识规则;强制它的东西是每一次偏离都在账本里看得见。

这个份额不会到期。它是发行的一个比例而不是一个币的数量,所以它随 §14.2 的补贴一同衰减,并在尾部持续为一股固定的名义流;而作为供给量的一个比例,它趋于零。移除它所要付的代价,就是任何一次共识变更所要付的代价:一次硬分叉。自 Era 2 起,这个 5 选 3 是这个协议唯一一个受信任的法定人数。

14.2 发行#

补贴平滑地衰减到一个永久的尾部。速率在一个纪元之内是恒定的,并在每个纪元边界上下调一级,由四个常数之上的精确整数算术给出:

E(0) = E₀
E(n) = max(tail, E(n−1) − E(n−1)/Q)

其中 L 是以区块计的纪元长度,Q 是衰减除数,E₀ 是创世补贴,E(n) 是整个纪元 n 中每区块的补贴。这四个 — L、Q、E₀、tail — 才是常数;C,即这份时间表所加总出的前尾部供给量,是通过运行这个递推式从它们导出来的,而不是它的一个输入。早先的修订版把第一行写作 E(0) = C / (L · Q),读起来就好像 C 是一个把补贴从中除出来的常数;那是无穷几何衰减的闭式解,而下面这份有限的时间表加总起来并不等于它。这里没有浮点数,也没有会写错的表格,更没有减半悬崖:每一级都小到没有哪一个单独的区块会撞见它,这与让 Era 2 时代的奖励成为一道斜坡是同一套推理。一旦这个公式跌到尾部之下,补贴就成为每区块一个固定的数额,永远如此。

这份时间表是高度的一个纯函数。它不查阅实际付出了多少,这意味着一个付得少于时间表的区块 — 比如一笔 coinbase 欠给了一个其持有者已把它烧掉的地址 — 是相对于 C 的一笔永久性短缺,而不是这条曲线日后会补上的一笔债。因此 C 是这份时间表所加总出的那个数字,而不是一个它保证会达到的上限。

尾部之所以存在,是因为安全是一项永久开支:它付给保障当前时代的那些人 — 先是矿工,然后是矿工加见证者,再然后是验证者 — 而不必征用 §8 的那两个手续费市场。它的规模被设定成让每年的尾部发行量从低于流通供给量的 1% 起步;由于分子是固定的而供给量在增长,这个百分比只会往下走。与它相抵的是 §8 的串行基础费销毁,所以净发行量是尾部减去销毁,在负载之下可以为负。§14.1 的 97/3 拆分适用于每一笔补贴,尾部也包括在内。

常数(创世,示意性的,同 §13):纪元 L = 2,880 个区块;衰减除数 Q = 1054;创世补贴 E₀ = 21/区块;尾部 0.33/区块。在 30 秒出块下,这些给出 0.70702 的年度系数,也就是说发行速率每两年减半 — 而且是精确地,不是近似地:E(730) = 10.50230028,相对于 E₀ = 21。这个公式在纪元 4,376、高度 12,602,880、约第 12 年跌到尾部之下,所以那条衰减的臂跑过纪元 0 到 4,375,而 C,即它所加总出的供给量,是 62,744,838.47。其中实际发行的是 62,744,817.47:差额是创世区块的那 21,它根本不支付任何 coinbase,因为没有矿工可付,而一个可复现的创世也不得贷记任何地址。于是每年的尾部发行量约为 34.7 万,即已发行供给量的 0.55%,并从那里往下走。**闭式解 L · E₀ · Q = 63,745,920 是 C 的一个上界,而不是它的值**,而这个差距不是舍入误差:那个乘积是无穷几何衰减之和,而这份时间表是有限的、每一级都向下取整、并且在尾部停住。它比 C 高出 1,001,081.53,即 1.60%。要引用那个加总值,永远不要引用闭式解。合规就是复现那个递推式 — 没有哪个节点会在任何高度上计算 C,或者本段中的任何其他数字。

15. 测量#

一个其核心主张是一种吞吐量不对称的设计,欠读者一些数字。下面的数据来自参考实现,可以从它已公布的源码重新导出。硬件:一颗十核 x86 台式机 CPU。数据为 5 次运行的中位数。

  • 折叠吞吐量:在 100,000 个槽位的工作集上,每核每秒 883,000 次槽位操作,即在每份凭证 3 个槽位时,每秒 294,000 份被应用的凭证。
  • 无状态验证:每 CPU 核心每秒 1,470 份凭证,10 个核心合计每秒 5,220 份 — 这个比值就是那个并行性主张,是测出来的而不是断言出来的。
  • 签名验证:每秒 1,500 次,单次进行,且已包含小阶检查和扭曲检查。
  • 端到端:一个含 2,900 份凭证的区块,验证耗时 1,963 毫秒,折叠耗时 9.9 毫秒。两者分开报告,是因为它们是那个论证的两半:前者随着这个网络并不拥有的核心扩展,后者是它确实拥有的那一个循环。

对折叠的这次测量是一次减法,而说出这一点也是报告它的一部分:提交一个区块会把无状态核查和串行折叠一起跑掉,所以折叠的那个数字是整体减去单独计时的那些核查。两半都在制品里。

我们只报告参考实现实际跑了的东西,而这划出两条值得点名、而不是留给读者自己去发现的界限。签名验证是一次一个签名地测量的:批量验证是一项真实的技术,也是一个显然的契合点,但那是这个项目还没有写过的安全关键密码学,而一个针对并不存在的代码给出的数字不是测量。§2 和 §6 所预期的那些 GPU 与 SIMT 数据,属于定序器的批量凭证,而它们随 Era 1 到来。两者都是设计上的预期。两者对上面那些数字都不承重:那个并行性主张所倚赖的是凭证可被独立核查,而批量处理只能让这件事更快,不能让它成立。

Zycord 的流水线 — 先执行、盲排序、提交时验证 — 在有许可的场景里有一位先祖:Hyperledger Fabric 的 execute-order-validate [3],其被记录在案的弱点是争用之下的中止率,以及对机构背书策略的依赖。相对于那条血脉,Zycord 的贡献是:(i)以值相等判定的可应用性,并把跳过作为一等的、容忍 ABA 的语义;(ii)冲突的经济归属 — 有抵押的承保,它让双签在客观上可罚没,让过时成为一项被定价的、可承保的服务,而这正是 execute-order-validate 要走向无需许可所欠缺的东西;以及(iii)强制打包并保证应用,而 rollup 的逃生舱只保证打包。确定性的、预先声明的读写集源自 Calvin [4],也出现在 Solana 的访问列表 [16] 中;可交换的托管方法可追溯到 O'Neil [9],今天则活在诸如 Aptos 建在 Block-STM [5] 之上的聚合器这类单节点调度器内部 — Zycord 把可交换性契约挪到节点之间,并为它定价。并行 EVM 系统 [5] 和延迟执行的链仍旧到处重新执行,并撞上状态 I/O 那堵墙;Narwhal [6] 像我们早期的设计一样把数据散播与排序分开;Sui 的自有对象与我们的一次写入存储单元相仿;Zether [7] 的 pending/base 划分启发了带守卫增量这套纪律(并且连同嵌合账本的隐私渊源 [8] 一起,回到了 §12 的机密支付里 — 那是繁重密码学车道的第一个租户,隐藏金额和一次性地址被定价进并行 gas,而不是被砌进基础层)。链式的终局性覆盖层沿袭 Casper FFG [12]。

17. 结论#

我们提出了一个为凭证排序而不是执行交易的网络:有效性由任何人、在任何地方、并行地、仅凭字节就能核查;状态由一个简单到一页纸就能规定的折叠推进;冲突被跳过、被定价、有归属;并发是被声明出来的而不是被发现出来的;审查在构造上就活得下去;而它的发布在要求任何人信任一个验证者集合之前,就先把币分发了出去。验证随着这个网络并不拥有的硬件向外扩展。提交保持串行 — 而且几乎免费。

Zycord 不去追着工作跑。它按兵不动,而网络在做工。

参考文献#

[1] S. Nakamoto. Bitcoin: A Peer-to-Peer Electronic Cash System. 2008. [2] V. Buterin. Ethereum: A Next-Generation Smart Contract Platform. 2013. [3] E. Androulaki et al. Hyperledger Fabric: A Distributed Operating System for Permissioned Blockchains. EuroSys 2018. [4] A. Thomson et al. Calvin: Fast Distributed Transactions for Partitioned Database Systems. SIGMOD 2012. [5] R. Gelashvili et al. Block-STM: Scaling Blockchain Execution by Turning Ordering Curse to a Performance Blessing. 2022. [6] G. Danezis et al. Narwhal and Tusk: A DAG-based Mempool and Efficient BFT Consensus. EuroSys 2022. [7] B. Bünz, S. Agrawal, M. Zamani, D. Boneh. Zether: Towards Privacy in a Smart Contract World. Financial Cryptography 2020. [8] J. Zahnentferner. Chimeric Ledgers: Translating and Unifying UTXO-based and Account-based Cryptocurrencies. IACR ePrint 2018/262. [9] P. E. O'Neil. The Escrow Transactional Method. ACM TODS, 1986. [10] M. Herlihy, E. Koskinen. Transactional Boosting: A Methodology for Highly-Concurrent Transactional Objects. PPoPP 2008. [11] M. Shapiro et al. Conflict-free Replicated Data Types. SSS 2011. [12] V. Buterin, V. Griffith. Casper the Friendly Finality Gadget. 2017. [13] M. Corallo. BIP 152: Compact Block Relay. 2016. [14] tevador et al. RandomX: Proof-of-Work Algorithm Optimized for General-Purpose CPUs. 2019. [15] EIP-1559: Fee Market Change for the ETH 1.0 Chain. Ethereum Improvement Proposals, 2019. [16] A. Yakovenko. Solana: A New Architecture for a High Performance Blockchain. 2017. [17] Decred Documentation: Issuance. docs.decred.org — 区块奖励在 PoW 矿工、PoS 投票者和国库之间的拆分。 [18] EIP-4844: Shard Blob Transactions. Ethereum Improvement Proposals, 2022. [19] Monero: Dynamic Block Weight and Penalty. 门罗币研究实验室文档;另见 JollyMort, Monero Dynamic Block Size and Dynamic Minimum Fee, 2017. [20] bitcoincashautist. CHIP-2023-04: Adaptive Blocksize Limit Algorithm for Bitcoin Cash. 2023.