ICICLE-Snark:目前最快的 Groth16 实现

1. 引言

零知识证明(Zero-Knowledge Proofs)近年来迅速发展,其中 Groth16 已成为最广泛使用的证明系统之一,具有许多非常适合区块链应用的特点:

  • 验证继续3次 pairing 运算,使Groth16成为验证速度非常快的 ZK 证明。
  • Groth16 生成的证明是固定大小,仅3个群元素,这比其它证明系统在链上存储和交易成本上更节省。
  • 虽然Groth16 证明者(prover)端计算量很大,但通过硬件加速可以显著加快,使 Groth16 在高吞吐量应用中也可行。

Groth16 最大的权衡是每个电路(circuit)都需要可信设置(trusted setup)。尽管这是一个缺点,但Groth16的以上特点,使其成为区块链及其它以外用途的理想选择 ------ 单个证明就能证明复杂计算的正确性而不暴露输入,任何人只需3次 pairing 就能验证。

2. Groth16 工作原理

生成一个 Groth16 证明需要在大的素域(prime field)和椭圆曲线群上执行大量算术运算。本文不深入 QAP(Quadratic Arithmetic Programs)和 R1CS(Rank-1 Constraint Systems)。

可以把Groth16证明者阶段分成两步:

  • 计算商多项式(quotient)
  • 和 生成证明。

在这些步骤中,会包含:

  • 3 个 IFFT(反快速傅里叶变换),
  • 3 个 FFT(快速傅里叶变换),
  • 以及 5 个 MSM(多标量乘法,多标量乘法是指多个标量与群元素的结合操作)。
  • 除此之外,还有element-wise 按元素乘法和减法等操作。

MSM 和 NTT/FFT 这两大原语掌控了Groth16 证明过程的大部分时间。

Groth16 证明者需要两个文件:witness 文件 与 zkey 文件。

  • witness 文件包含给定输入根据电路产生的中间值,包括公开与私密输入。对每个输入都是独一无二的。
  • zkey 文件则包含电路、可信设置、证明与验证密钥等,是在可信设置阶段生成的,对每个电路唯一。

3. 现有 Groth16 实现的状况

目前已有很多 Groth16 的实现,常见的包括:

4. 用 ICICLE 突破 Groth16 速度障碍

ICICLE 是一个前沿的密码学库(ICICLE-snark(https://github.com/ingonyama-zk/icicle-snark(C++和Rust))),旨在加速诸如 ZKP(零知识证明)等高级算法与协议,在多种计算后端(包括 GPU、CPU、Metal 等)上运行。它支持 C++、Rust、Go 多种前端,使得在不同开发环境中集成更加容易。只用一个代码库,就能利用三种流行语言和多个硬件平台。

4.1 ICICLE 加速 Groth16

起初Ingonyama团队只计划把 MSM 和 NTT 部分集成进 ICICLE,但由于数据传输与类型转换的问题,性能并没有达到预期。最终的解决方案是从头构建一个 Groth16 的 prover,该 prover 使用 ICICLE 实现。参考代码库是 (iden3 团队的)SnarkJS(https://github.com/iden3/snarkjs(JavaScript))。这样可以允许所构造的 prover 使用现有的 zkey 文件。任何人都可以从 SnarkJS 或 Rapidsnark 切换到 ICICLE-snark(https://github.com/ingonyama-zk/icicle-snark(C++和Rust))

  • ICICLE 已经对 第2节"Groth16 工作原理" 中提到的那些原语(MSM, FFT/NTT 等)做了高度优化。

另一个现有工具的问题在于,它们被设计为 命令行工具(CLI) 使用。

这意味着如果要多次为同一个电路生成证明,那么其实有许多数据是可以缓存并复用的,如:

  • 证明密钥(proving key)
  • 基(bases)
  • 和 NTT 域(NTT domain)。

为了利用这种缓存技巧,Ingonyama团将其Groth16 prover 开发成了一个 后台工作进程 和 Rust 库。

4.2 ICICLE 中的数据转换与数据传输

将代码迁移到 GPU 时,最大的问题之一是数据传输和数据类型转换。要能使用 ICICLE 并利用 CUDA,需要将数据转换为 ICICLE 的类型并移动到device端。通常为 CUDA kernel 准备数据的时间会比 kernel 本身执行时间还要长。

  • 类型转换(conversion)如果用 naïve 的方式(如多次调用 from 函数)会非常慢。在 Rust 中,如果可以保证安全,可以使用 transmute 直接改动已有内存的类型,这样几乎可以完全加速------transmute 调用只花费约 20--30 纳秒,而 naive 转换可能花费约 100 毫秒。
  • 数据传输速度受限于硬件。因此如果可能,应尽量避免将数据在host与device之间频繁移动。这是为什么团队选择从头构造 Groth16 prover 而不是仅替换部分 MSM/NTT 调用。

4.3 ICICLE 中的 MSM 与 NTT 优化

ICICLE 中的 MSM 与 NTT 优化有:

  • 替换5个 MSM 所用的实现为 ICICLE 的 MSM,在规模为 2 22 2^{22} 222 的情况下平均提升约 63倍。
  • 同样替换 3 个 IFFT + 3 个 FFT 为 ICICLE 的 FFT API,在规模为 2 22 2^{22} 222 的情况下平均提升约 320倍。

4.4 ICICLE 中的VecOps(向量运算)优化

ICICLE 中的VecOps(向量运算)优化:

  • 有很多地方需要对长向量做element-wise按元素的乘法与减法。这类操作在 GPU 上高度并行化。
    • 使用 ICICLE 的 VecOps API 而不是在 CPU 上处理这些长数组,平均提升约 200倍(在规模 2 22 2^{22} 222 时)。

4.5 ICICLE 中的缓存机制(Cache)优化

在许多应用场景中(如证明服务或 Layer-2 rollups),可能会对同一个电路和证明密钥(proving key)做多个证明。

  • ICICLE 利用这一点,把与 zkey 文件相关的计算保留在设备内存中,并缓存这些计算结果。称为 CacheManager。如果某次已经针对某个 zkey 文件算过某些值,下次用到就可以重用,不必重新计算。

5. 性能基准与实验结果

在以下两种设置上进行了基准测试:

  • 使用 4080 GPU + Intel i9-13900K CPU
  • 使用 4090 GPU + Ryzen 9 9950X CPU

用 MoPro(https://github.com/zkmopro/benchmark-app/tree/main/mopro-core/examples/circom) 基准里的电路来对比不同证明系统的性能。测试包括:

  • "复杂电路":用于纯粹基准测试,以便对比约束数量多寡与性能差异。
  • Anon Aadhaar:一个零知识协议,允许 Aadhaar 身份所有者在不暴露其身份信息的情况下证明身份。
  • Aptos Keyless:Aptos Keyless 让用户无需密钥或助记词,而使用诸如 OIDC 凭证(Google, Apple 等)来创建账户。

如前所述,在生产环境中,一个项目更有可能重复证明相同的电路。

为了利用这一点,ICICLE使用了 缓存系统(Cache system)。

然而,由于所对比的其他工具都是 命令行工具(CLI),因此在一次证明完成后就会退出。

为了保持公平,同时提供了 启用缓存 和 不启用缓存 两种基准测试结果。





6. 如何集成 ICICLE

可将 ICICLE-snark 集成到自身项目的代码库里。根据使用的语言有两种方式:

  • 在 Rust 项目中使用
  • 在其他编程语言中使用

6.1 在 Rust 项目中使用 ICICLE

如果自身项目代码库是用 Rust 编写的,那么最佳实践是将ICICLE Groth16 prover作为 Rust Crate 使用。

只需要通过执行:

复制代码
cargo add ICICLE-snark

将其添加到依赖中。

之后,就可以导入证明函数,并创建一个 CacheManager 实例,然后通过提供 witness 文件 和 zkey 文件 的路径来开始证明。

rust 复制代码
use icicle_snark::{groth16_prove, CacheManager};

fn main() {
    let mut cache_manager = CacheManager::default();
    let witness = "witness.wtns";
    let zkey = "circuit_final.zkey";
    let proof = "proof.json";
    let public = "public.json";
    let device = "CUDA"; // 若需可以替换为 CPU 或 METAL

    groth16_prove(
        &witness,
        &zkey,
        &proof,
        &public,
        device,
        &mut cache_manager
    ).unwrap();
}

6.2 在其他编程语言中使用 ICICLE

如果自身项目代码库是用其它语言写的,也可以使用 ICICLE prover。方式是用"worker 模式"(worker mode)运行 ICICLE-snark,然后从项目代码库与ICICLE worker 通信。ICICLE 的例子目录里有相关用了该模式的示例。

参考资料

1\] 2025年3月18日Ingonyama团队博客 [ICICLE-Snark: The Fastest Groth16 Implementation in the World](https://medium.com/@ingonyama/icicle-snark-the-fastest-groth16-implementation-in-the-world-00901b39a21f)

相关推荐
搬砖魁首1 个月前
密码学系列 - 零知识证明(ZKP) - 多种承诺方案
密码学·零知识证明·pcs·zkp·承诺方案
AIMercs2 个月前
零知识证明
区块链·零知识证明
搬砖魁首2 个月前
DevCon 6记录
零知识证明·zk·账户抽象·mpc钱包·devcon
胡耀超4 个月前
探讨零知识证明的数学原理与应用
python·web安全·区块链·密码学·数据安全·零知识证明
forestsea5 个月前
零知识证明:区块链隐私保护的变革力量
区块链·零知识证明
拾忆-eleven5 个月前
解构区块链身份认证:从ID到零知识证明的实战指南
区块链·零知识证明
人类群星闪耀时5 个月前
区块链技术在数据隐私保护中的应用:从去中心化到零知识证明
去中心化·区块链·零知识证明
搬砖魁首6 个月前
2024年零知识证明(ZK)研究进展
区块链·零知识证明·zk·sumcheck·sum-check
电报号dapp1196 个月前
下一代代币技术白皮书:合规化智能合约与零知识证明隐私协议
web3·去中心化·区块链·智能合约·零知识证明