zkVM 与证明市场:RISC Zero、SP1、递归聚合与 prover 经济

从 zkVM 的通用可证明计算出发,系统讲解 RISC-V 指令集选择的工程理由、RISC Zero 的 guest/host 分工与 receipt/journal 模型、SP1 的 precompile 与两段式证明、执行轨迹到链上验证的完整流程、递归证明与证明聚合、STARK 包装为 Groth16 的成本结构,以及 prover network 的拍卖竞价与经济激励,并给出 Rust guest 与 Solidity 验证合约示例。

导语:把写电路变成写程序

零知识证明曾经是电路工程师的专属领域:为了证明一段逻辑,你必须把它翻译成 R1CS 约束,逐门优化,再为它做一次可信设置。zkVM(Zero-Knowledge Virtual Machine,零知识虚拟机)改变了这件事——它把「写电路」变成了「写程序」。你用 Rust 写一个普通函数,编译成 RISC-V 指令,zkVM 就能为这段程序的执行生成一份密码学证明。

一句话总结:zkVM 用「通用指令集的解释器电路」替代了「每个应用一份专用电路」,代价是每条指令都要被约束,收益是任何程序无需改电路即可被证明。

zkVM 与证明市场是一枚硬币的两面:前者解决了「证明什么」的通用性问题,后者解决了「谁来证明、多少钱、多快」的供给问题。当证明生成变成一种可外包、可竞价的服务,链上应用就获得了「按需计算」的能力——这正是证明市场试图构建的经济层。


1. zkVM 的概念与指令集选择

1.1 从专用电路到通用虚拟机

专用电路(如 circom 写的 Merkle 证明)把业务逻辑直接编码进约束系统,效率极高但毫无复用性:改一行逻辑就要重做可信设置、重新审计、重新部署验证合约。zkVM 的思路是把「执行」抽象出来:

应用代码(Rust/C/C++)
   │  编译
   ▼
目标指令集(RISC-V RV32IM)
   │  执行
   ▼
执行轨迹(execution trace):每一步的寄存器、内存、程序计数器
   │  算术化(arithmetization)
   ▼
约束系统:约束「每一步都是正确的指令语义」
   │  多项式承诺 + FRI / KZG
   ▼
证明(STARK 或 SNARK)
   │  验证
   ▼
链上验证合约:只需知道「程序映像 ID」与「公开输出」

关键洞察在于:约束系统只需要描述指令语义(比如「add 指令:rd = rs1 + rs2」),而与应用无关。因此同一套 zkVM 可以证明任意程序,链上只需要一个验证合约,通过程序映像 ID(program image ID,即编译产物 ELF 的哈希)区分不同的 guest 程序。

1.2 为什么 RISC-V 成为事实标准

选择指令集不是审美问题,而是证明成本问题。RISC-V 胜出的原因可以拆成四点:

维度RISC-V 的优势对证明成本的影响
指令集规模基础指令少且规整(RV32IM 约 40 余条)需要实现的约束门更少,电路更小
解码规则定长 32 位、字段位置固定解码约束可用查表实现,无需复杂分支
生态与工具链LLVM/GCC 后端成熟,Rust 一等公民guest 代码几乎零改动即可编译
可扩展性预留 custom opcode 空间可为 precompile 分配专用指令

对比之下,x86 是变长指令、解码极其复杂,把 x86 完整塞进约束系统会让解码本身成为瓶颈;EVM 则是为区块链设计的栈式字节码,虽然语义清晰,但它是领域专用的——用它做通用计算会非常别扭(这也是 zkEVM 与 zkVM 的分野起点)。

1.3 zkVM 与 zkEVM 的分野

很多人把两者混为一谈,但它们的优化目标相反:

维度zkVMzkEVM
目标证明任意程序的执行证明以太坊区块/交易的状态转换
指令集RISC-V(通用)EVM 字节码(领域专用)
兼容性诉求语言级(Rust/C 可编译即可)字节码级(EVM 等价)
典型场景跨链桥、预言机、ZK 机器学习、协处理器Rollup、L2 状态证明
证明开销来源通用指令的解释成本EVM 语义(存储、gas、状态)的建模成本
代表项目RISC Zero、SP1、Jolt、NexusScroll、Polygon zkEVM、zkSync Era

一句话总结:zkEVM 追求「让以太坊跑得动证明」,zkVM 追求「让任意程序跑得动证明」——前者为 L2 服务,后者为链上协处理器与跨链验证服务。


2. RISC Zero:通用 zkVM 的工程范式

2.1 guest 与 host 的分工

RISC Zero 把程序分成两半,这个划分是理解一切 zkVM 的钥匙:

  • guest:运行在 zkVM 内部的代码,它的每一步执行都会被约束、被证明。guest 只能读取显式传入的输入,写出的输出会进入 journal。
  • host:运行在 zkVM 外部的普通程序(你的主程序),它负责准备输入、调用 prover、拿到 receipt、提交到链上。host 的计算不被证明,因此可以随意调用网络、文件系统、GPU。
// methods/guest/src/main.rs —— 运行在 zkVM 内部,逐条指令被约束
use risc0_zkvm::guest::env;
use sha2::{Digest, Sha256};

fn main() {
    // 从 host 读取私有输入:这些字节不会出现在证明的公开部分
    let secret: Vec<u8> = env::read();
    let commitment: [u8; 32] = env::read();

    let mut hasher = Sha256::new();
    hasher.update(&secret);
    let digest: [u8; 32] = hasher.finalize().into();

    // 断言失败 → 无法生成有效证明(这是可靠性的来源)
    assert_eq!(digest, commitment, "secret does not match commitment");

    // 写入 journal:公开输出,随 receipt 一起上链
    env::commit(&digest);
}
// host/src/main.rs —— 运行在 zkVM 外部,不被证明
use risc0_zkvm::{default_prover, ExecutorEnv};
use methods::{HASH_GUEST_ELF, HASH_GUEST_ID};

fn main() {
    let secret = b"correct horse battery staple".to_vec();
    let commitment: [u8; 32] = Sha256::digest(&secret).into();

    let env = ExecutorEnv::builder()
        .write(&secret).unwrap()
        .write(&commitment).unwrap()
        .build().unwrap();

    // 默认生成 STARK 证明;如需链上验证则包装为 Groth16
    let receipt = default_prover().prove(env, HASH_GUEST_ELF).unwrap();

    // 本地先自检一遍,避免把无效证明提交到链上浪费 gas
    receipt.verify(HASH_GUEST_ID).unwrap();

    // 上链三要素:seal(证明字节)、image_id(程序标识)、journal(公开输出)
    println!("image_id = {:?}", HASH_GUEST_ID);
    println!("journal  = {:?}", receipt.journal.bytes);
}

2.2 receipt 与 journal

RISC Zero 把「证明」拆成三个可独立传递的部件,理解它们的边界是避免踩坑的前提:

  • journal:guest 通过 env::commit() 写出的公开输出,本质是一段字节串。链上合约只信任 journal 的内容,因为它是被证明覆盖的。
  • seal:真正的证明字节。STARK 形态下是几十到几百 KB;Groth16 包装后约 256 字节。
  • image_id:guest ELF 的哈希,标识「这段程序」的身份。链上验证时必须校验 image_id,否则攻击者可以用一个更弱的 guest 程序生成「合法」证明。

关键安全边界:journal 里的数据是被证明的,但读取 journal 的合约必须自己校验 image_id。很多早期集成事故的根因就是验证合约只验了证明有效,却没绑定 image_id,导致任何人可以用自己的程序伪造 journal 内容。

2.3 Groth16 包装与链上验证

STARK 证明无法在以太坊上直接验证(验证成本数百万 gas)。RISC Zero 的做法是:在链下用递归电路把整个 STARK 验证过程再证明一次,输出一个 Groth16 证明(seal),链上只需一个约 250k gas 的配对检查。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol";

contract CommitReveal {
    IRiscZeroVerifier public immutable verifier;
    // 只信任这一个 guest 程序:image_id 是编译产物 ELF 的哈希
    bytes32 public immutable imageId;
    bytes32 public latestCommitment;

    constructor(IRiscZeroVerifier _verifier, bytes32 _imageId) {
        verifier = _verifier;
        imageId = _imageId;
    }

    function submit(bytes calldata seal, bytes calldata journal) external {
        // 1) 验证证明:seal 覆盖了 sha256(journal),且 imageId 必须是白名单程序
        verifier.verify(seal, imageId, sha256(journal));

        // 2) journal 已被证明覆盖,可以安全解码业务数据
        bytes32 commitment = abi.decode(journal, (bytes32));
        latestCommitment = commitment;
    }
}

3. SP1:RISC-V zkVM 的性能路线

3.1 SP1 的整体架构

SP1 是 Succinct 团队推出的 RISC-V zkVM,工程目标非常明确:把每周期证明成本压到最低。它的核心设计选择是「不用通用查表证明一切,而是为高频操作开专用通道」:

SP1 证明栈:
  Rust guest 代码
    │  rustc (riscv32im-succinct-zkvm-elf)
    ▼
  SP1 核心 VM(RV32IM + 自定义 opcode)
    │  执行 → 轨迹分片(shard)
    ▼
  分片证明:每个 shard 生成一个 STARK 证明
    │  递归聚合(core → compress → shrink → wrap)
    ▼
  PlonK 证明(中间态,约 1 秒验证)
    │  再包装
    ▼
  Groth16 证明(约 256 字节,链上约 250k gas)

分片(shard)是关键:单次执行被切成固定大小的片段并行证明,再通过递归把多个分片证明聚合成一个,这既控制了内存占用,也让 GPU 集群可以横向扩展。

3.2 precompile 与预编译表

纯 RV32IM 解释器证明的致命弱点是:一条 SHA-256 压缩函数要拆成上千条基础指令,证明成本被放大几个数量级。SP1 与 RISC Zero 都引入了 **precompile(预编译)**机制:把高频密码学操作做成专用约束,成本降到原来的几十分之一。

操作纯 RV32IM 证明成本使用 precompile典型加速比
SHA-256 压缩约 2000 周期约 30 周期60 倍以上
Keccak-256约 3000 周期约 40 周期70 倍以上
secp256k1 签名恢复数十万周期约 2000 周期100 倍以上
bn254 配对百万周期量级约 1 万周期100 倍以上

代价是:precompile 一旦被使用,guest 程序就与特定 zkVM 版本绑定,无法跨 zkVM 移植;而且 precompile 的约束若写错,会引入健全性漏洞——这是审计 zkVM 时最需要盯的地方。

3.3 PlonK 与 Groth16 的两段式证明

SP1 的证明管线是典型的「两段式」:STARK 负责处理大计算量(透明、可并行、适合 GPU),SNARK 负责链上验证(简洁、验证快)。

STARK(core 证明,每个 shard 一个)
  → 递归聚合(recursion)
  → shrink:压缩证明大小,降低后续电路规模
  → wrap:转为 PlonK(约 1 秒验证,可用于链下或 L2)
  → 可选再包装为 Groth16(链上最省 gas)

工程取舍:如果验证发生在你自己的 L2 或链下服务上,PlonK 已经够用(约 1 秒验证但 gas 更高);如果验证发生在以太坊 L1,几乎一定要走 Groth16 包装,因为包装本身要花 10~60 秒。


4. 证明生成与验证的完整流程

4.1 执行轨迹与 witness 生成

一切从执行轨迹开始。zkVM 逐条执行 guest 指令,把每一步的状态记录下来:

轨迹的每一行 = 一个「执行周期」(cycle):
  [ pc, 指令字, rs1_val, rs2_val, rd_val, 内存读写地址与值, 内存 Merkle 根 ]

一个 100 万周期的 guest 程序,轨迹就有 100 万行。这个规模决定了后面所有成本:轨迹越大,约束系统越大,多项式承诺的计算量越大,证明时间越长。

4.2 约束系统与多项式承诺

轨迹本身不是证明,它需要被「算术化」成约束。核心约束分三类:

  1. 执行约束:每一步的 pc、寄存器、内存变化必须符合指令语义(如 add 要求 rd = rs1 + rs2)。
  2. 连续性约束:上一步的内存状态与下一步必须一致,程序计数器转移必须合法。
  3. 边界约束:初始状态与最终状态必须与公开输入/输出一致。

然后把约束转成多项式恒等式,用多项式承诺(STARK 用 FRI,SNARK 用 KZG)来承诺,并证明「这些多项式在某个随机点上满足恒等式」。验证者只需检查少数几个点,这就是简洁性(succinctness)的来源。

4.3 从证明到链上验证

1. 生成阶段(链下、GPU 集群)
   guest ELF + 输入 → 执行 → 轨迹 → 约束 → STARK 证明(分片)
2. 聚合阶段(链下、递归)
   多分片 STARK → 递归证明 → shrink → wrap → PlonK/Groth16
3. 提交阶段(链上)
   交易携带 seal + journal(几百字节到 256 字节)
4. 验证阶段(链上、常数时间)
   验证合约:配对检查 + sha256(journal) 比对 + image_id 白名单校验

链上验证的成本是常数的,与 guest 执行了多久无关——这正是 zkVM 最迷人的性质:链下算一小时,链上验证一毫秒。


5. 证明聚合与递归

5.1 递归证明的两种形态

递归证明(recursive proof)指「证明中包含另一个证明的验证」。它在 zkVM 里有两种用途:

  • 同构递归:用同一个 zkVM 证明「我验证了前一个证明且执行了新的一个分片」。这是分片聚合的基础,SP1/RISC Zero 都用它把 N 个分片证明压成 1 个。
  • 异构递归:用一个证明系统去证明另一个证明系统的验证器(如「用 RISC Zero 证明 Groth16 验证通过」),这是 STARK 到 SNARK 包装的本质。

5.2 证明聚合的工程实现

分片 1 → STARK 证明 P1
分片 2 → STARK 证明 P2
分片 3 → STARK 证明 P3
   │
   ├─ 递归节点 A:证明「P1 有效 且 P2 有效」 → PA
   └─ 递归节点 B:证明「PA 有效 且 P3 有效」 → PB
   │
   ▼
最终证明 PB:大小恒定,与分片数量无关

聚合的价值不只是「变小」,更是让验证成本不随计算量线性增长。一个跑 100 亿周期的 guest,如果不用聚合,链上要验证 1 万个分片证明;聚合后仍然只验证一个。

5.3 递归带来的成本结构变化

递归不是免费的午餐,它有自己的成本特征:

阶段成本量级说明
核心 STARK 证明与周期数近似线性主要开销,可 GPU 并行
递归聚合每个递归节点固定开销节点数约为分片数的对数或线性
shrink 与 wrap固定开销,10~60 秒与电路规模相关,难以并行
链上验证约 250k gas与计算量无关

实践中的坑:分片切得太小,递归节点数爆炸,聚合成本反而超过证明成本;切得太大,单分片内存溢出(OOM)。SP1 的默认分片大小(约 2^21 周期)是经过大量调优的经验值,不要随意改。


6. STARK 到 SNARK 的包装与成本

6.1 为什么必须做包装

STARK 在以太坊上不可直接验证,原因很实在:FRI 验证需要做几十次 Merkle 路径校验和哈希运算,链上执行要数百万 gas。包装(wrapping)的思路是:把「STARK 验证器的逻辑」写成一段 guest 程序,用 zkVM 再证明一次,输出一个 Groth16 证明。

STARK 证明(几百 KB,链下验证约 10 ms)
   │  作为输入喂给一个「STARK 验证器 guest」
   ▼
Groth16 证明(约 256 字节,链上验证约 250k gas)

6.2 包装的成本模型

包装是固定成本,与原始计算量无关,因此对「小计算」而言包装可能比证明本身还贵:

总成本 = 核心证明成本(周期数 × 每周期单价)
       + 聚合成本(分片数 × 递归单价)
       + 包装成本(固定,约 10~60 秒 GPU 时间)

当周期数很小时(如 10 万周期),包装成本占比可达 80% 以上
当周期数很大时(如 10 亿周期),包装成本被摊薄到可忽略

结论:小任务不要走 Groth16 包装,直接用 STARK 或 PlonK 在链下/L2 验证更划算。这也是很多「用 zkVM 证明一个小哈希」的教程看起来昂贵的原因——它们的瓶颈不在证明而在包装。

6.3 验证合约的 gas 对比

证明系统证明大小链上验证 gas是否需可信设置备注
Groth16约 256 字节约 250k需要(电路特定)链上最省
PlonK约 800 字节约 300k需要(通用 setup)验证稍慢
STARK(链上)数十 KB数百万不需要实际不可行
无证明(直接执行)—与计算量成正比—无简洁性

Groth16 是链上验证的默认选择,代价是每个 guest 程序(严格说是每个电路)都需要一次 setup,且 setup 结果随 guest 代码变化而失效——所以guest 程序一旦部署就不要轻易改,改了就要重新 setup 并更新链上验证密钥。


7. 证明市场与激励设计

7.1 prover network 的角色构成

单机证明有两个问题:成本高(需要 GPU 集群)与可用性差(单点故障)。证明市场把证明生成外包给一个去中心化的网络:

角色职责激励来源
请求方(requester)提交证明请求与悬赏获得证明,解锁业务
证明者(prover)运行 GPU 集群生成证明获得悬赏与代币奖励
验证者(verifier)抽样验证证明正确性质押收益与举报奖励
协议(protocol)匹配、结算、罚没协议费

7.2 拍卖与竞价机制

证明市场本质上是一个计算资源拍卖行,主流设计有三种:

  • 请求出价(request-for-quote):请求方挂出任务与最高价,prover 竞价接单。简单但容易价格战。
  • 荷兰拍(Dutch auction):价格从高往低递减,直到有 prover 接单。适合有明确时限的任务。
  • 批量拍卖(batch auction):把一段时间窗口内的所有请求打包,统一分配与定价。能缓解抢跑与价格波动,是较先进的方案。
一次典型竞价流程:
  1. 请求方锁定悬赏金额(escrow)
  2. 广播任务元数据:image_id、输入哈希、周期上限、截止时间
  3. prover 提交报价(可加密,避免互相窥探)
  4. 协议选出中标者(最低价 / 最可靠 / 加权评分)
  5. prover 生成证明并提交
  6. 链上验证通过 → 释放悬赏;失败 → 罚没质押

7.3 质押、罚没与经济模型

没有质押就没有可靠性。证明市场的核心安全假设是:作恶的成本必须高于收益。典型设计:

  • prover 需质押代币才能接单,罚没金额与任务价值挂钩。
  • 提交错误证明 → 全额罚没 + 信誉归零。
  • 超时未提交 → 部分罚没,任务重新分配。
  • 引入信誉分:高分 prover 可接更大额任务、获得优先匹配,形成正循环。

一个容易被忽略的激励缺口是验证者困境:如果验证成本高于举报奖励,理性参与者不会验证,作恶就有空间。主流做法是乐观抽样验证——随机抽取一小部分证明做冗余验证,用概率保证安全,同时把验证成本摊薄。

7.4 成本与延迟评估

以下数据是量级参考(实际随硬件、zkVM 版本、电路复杂度显著波动),用于做容量规划:

指标CPU 单机中端 GPU 单卡8 卡 GPU 集群
STARK 证明吞吐约 1M 周期/秒约 10M 周期/秒约 80M 周期/秒
1000 万周期证明耗时约 10 秒约 1 秒约 0.15 秒
10 亿周期证明耗时约 17 分钟约 100 秒约 15 秒
Groth16 包装不推荐约 30~60 秒约 20~40 秒
每周期成本(估算)基准 1x约 0.1x约 0.02x

延迟分层:STARK 证明可以做到秒级,但加上聚合与 Groth16 包装后,端到端延迟通常落在 30 秒到数分钟。如果业务要求「亚秒级最终性」,zkVM 包装路线基本不可行,应改用可信执行环境(TEE)或委员会签名方案。

7.5 工程落地与常见踩坑

  1. 忘记校验 image_id:验证合约只验证明有效而不校验程序身份,等于允许任何人用任意程序伪造 journal。这是最高频、最严重的集成漏洞。
  2. guest 代码里用了不支持的标准库:guest 运行在 no_std 或受限环境,std::fs、std::net、std::thread 全部不可用,必须改写。
  3. guest 计算量失控:一个不小心引入的 println! 或循环会把周期数推高一个数量级,证明成本爆炸。务必用 env::cycle_count() 或 profiling 工具盯住周期数。
  4. 误以为 host 的计算被证明:host 里做的任何校验都是「本地自检」,不可信。只有 journal 里的内容才是被证明的。
  5. 包装成本被忽略:小任务硬上 Groth16,成本主要花在包装上。评估时一定要算端到端成本,而不是只看 STARK 证明时间。
  6. precompile 版本漂移:升级 zkVM 版本后 precompile 约束可能变化,验证密钥与 image_id 都会变,必须同步更新链上合约,否则旧证明全部失效。
  7. 未做证明重放防护:同一份证明可以被重复提交。合约必须维护 nonce 或已消费 journal 哈希集合。
  8. 低估内存:大电路证明的峰值内存可达数百 GB,节点规格不足会 OOM 而不是变慢,扩容前先确认内存曲线。

8. 总结

  1. zkVM 的本质:用通用指令集解释器电路换取应用无关性,代价是每条指令都要被约束。
  2. RISC-V 是工程最优解:指令规整、工具链成熟、可扩展 opcode,让 guest 代码几乎零改动。
  3. RISC Zero 范式:guest 被证明、host 不被证明,receipt 由 seal、journal、image_id 三部分组成,链上验证必须校验 image_id。
  4. SP1 的性能路线:precompile 把密码学操作成本降两个数量级,分片加递归让 GPU 集群可横向扩展。
  5. 证明流程:执行轨迹到约束到多项式承诺再到链上常数时间验证,链上成本与计算量解耦。
  6. 递归与聚合:让验证成本不随计算量线性增长,但分片粒度选择直接影响总成本与内存。
  7. 包装是固定成本:小任务不要走 Groth16,端到端成本才是决策依据。
  8. 证明市场的核心是激励相容:质押罚没保证可靠性,抽样验证解决验证者困境,拍卖机制决定定价效率。

一句话总结:zkVM 把「可证明计算」从电路工程变成了普通编程,证明市场把「生成证明」从自建机房变成了可竞价的服务——两者结合,才让零知识证明第一次具备了规模化落地的经济基础。


相关阅读


延伸阅读


以下是一个完整的端到端示例:guest 程序在 zkVM 内验证一个 Merkle 成员证明,host 生成并包装证明,链上合约消费 journal 并更新状态根。

// methods/guest/src/main.rs
// 证明:我知道 (leaf, path) 使得 Merkle 根等于公开的 root
use risc0_zkvm::guest::env;
use sha2::{Digest, Sha256};

fn hash_pair(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
    let mut h = Sha256::new();
    h.update(left);
    h.update(right);
    h.finalize().into()
}

fn main() {
    let leaf: [u8; 32] = env::read();
    let path: Vec<[u8; 32]> = env::read();
    let indices: Vec<bool> = env::read();
    let expected_root: [u8; 32] = env::read();

    let mut current = leaf;
    for (sibling, go_right) in path.iter().zip(indices.iter()) {
        current = if *go_right {
            hash_pair(&current, sibling)
        } else {
            hash_pair(sibling, &current)
        };
    }

    // 约束:计算结果必须等于公开根,否则证明无法生成
    assert_eq!(current, expected_root, "merkle path invalid");

    // 公开输出:只暴露根,不暴露 leaf 与 path
    env::commit(&expected_root);
}
// host/src/main.rs
use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts};
use methods::{MERKLE_GUEST_ELF, MERKLE_GUEST_ID};

fn prove_membership(
    leaf: [u8; 32],
    path: Vec<[u8; 32]>,
    indices: Vec<bool>,
    root: [u8; 32],
) -> Vec<u8> {
    let env = ExecutorEnv::builder()
        .write(&leaf).unwrap()
        .write(&path).unwrap()
        .write(&indices).unwrap()
        .write(&root).unwrap()
        .build().unwrap();

    // 指定 Groth16 包装,产物适合直接上链
    let opts = ProverOpts::groth16();
    let receipt = default_prover()
        .prove_with_opts(env, MERKLE_GUEST_ELF, &opts)
        .unwrap();

    // 链下自检:无效证明绝不提交
    receipt.verify(MERKLE_GUEST_ID).unwrap();

    // 返回 seal,供链上验证合约使用
    receipt.inner.groth16().unwrap().seal.clone()
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol";

contract MerkleRootRegistry {
    IRiscZeroVerifier public immutable verifier;
    bytes32 public immutable imageId;

    bytes32 public currentRoot;
    mapping(bytes32 => bool) public consumedJournal; // 重放防护

    event RootUpdated(bytes32 indexed root, uint256 timestamp);

    constructor(IRiscZeroVerifier _verifier, bytes32 _imageId) {
        verifier = _verifier;
        imageId = _imageId;
    }

    function updateRoot(bytes calldata seal, bytes calldata journal) external {
        // 1) 重放防护:同一份 journal 只能消费一次
        bytes32 journalHash = sha256(journal);
        require(!consumedJournal[journalHash], "journal already consumed");

        // 2) 验证证明,并绑定到白名单 guest 程序
        verifier.verify(seal, imageId, journalHash);

        // 3) journal 已被证明覆盖,解码业务数据
        bytes32 root = abi.decode(journal, (bytes32));
        require(root != bytes32(0), "empty root");

        consumedJournal[journalHash] = true;
        currentRoot = root;

        emit RootUpdated(root, block.timestamp);
    }
}

上述三段代码构成一个完整的闭环:guest 定义「什么必须为真」,host 负责「把证明生出来并包装」,Solidity 合约负责「在链上以常数成本验证并消费结果」。理解了这三者的职责边界,就掌握了 zkVM 应用的基本骨架——而证明市场则是在此之上,把 host 这一环从「自己跑」变成「买服务」的经济扩展。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「blockchain」更多文章

  1. Rollup 序列器、L3 与应用链
  2. 账户抽象:ERC-4337、智能合约钱包与 Paymaster
  3. 零知识证明:zk-SNARKs、zk-STARKs、电路与隐私应用