如何用 bellman 生成 Groth16 证明?

解读

国内区块链、隐私计算团队面试时,“能不能自己搭一条零知识证明流水线” 是区分“调库工程师”与“底层开发”的分水岭。bellman 是 Zcash 官方维护的 Groth16 实现,用纯 Rust 写成,无标准库依赖 即可跑 WASM,性能对标 libsnark。面试官问“怎么用 bellman 生成证明”,并不是让你背 API,而是考察:

  1. 是否真正理解 Groth16 的三方协议流程(Setup→Prove→Verify);
  2. 能否把 算术电路 → R1CS → QAP 的数学对象映射到 bellman 的 Circuit、ConstraintSystem、ProofSystem trait;
  3. 是否熟悉 可信设置(MPC) 在国内合规场景下的落地姿势(谁跑 setup、参数怎么存、怎么审计);
  4. 是否具备 内存安全 + 零拷贝 的 Rust 编码习惯,避免在证明现场触发 OOM 或侧信道泄漏。

一句话:“给你 bellman,能不能在国产联盟链里跑出生产级 Groth16?”

知识点

  1. Groth16 算法框架
    离线 Setup 生成 proving key、verifying key;Prove 阶段 witness + pk → proof;Verify 阶段 proof + vk → bool。
  2. bellman 核心 trait
    Circuit<Fr>:定义电路结构;
    ConstraintSystem<Fr>:分配变量、加约束;
    ProofSystem:create_random_proof、verify_proof。
  3. Scalar 域与曲线
    bellman 默认 BLS12-381 曲线,Fr 是 255 位素数域;国内项目若需国密替代,需自行实现 SM2 嵌入域 的 Engine。
  4. 可信设置 MPC
    bellman 提供 phase1、phase2 两个仪式;phase2 必须针对具体电路,参数文件可达数百 MB,需通过 “分片+哈希” 上链存证,满足《区块链信息服务管理规定》审计要求。
  5. 内存与并发
    bellman 证明阶段单线程峰值内存 ≈ 每百万门 2.5 GB;需用 jemalloc + hugepage,并把 “witness 计算” 与 “证明计算” 拆分到 rayon 线程池,防止 tokio 阻塞。
  6. 国产化适配
    若目标平台为 鲲鹏 920 或飞腾 ARM64,需打开 --features asm 并交叉编译 bls12_381 的汇编后端;在 海光 x86 上则开启 adx/bmi2 指令集。

答案

下面给出一条 “可在国产 BSN 开放联盟链上直接落地” 的最小完整示例,全部代码 #![no_std] 兼容,并附带 内存安全与合规注释

步骤 1:Cargo.toml 精确依赖

[dependencies]
bellman = { version = "0.14", default-features = false, features = ["groth16"] }
bls12_381 = { version = "0.8", features = ["groups"] }
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
hex = { version = "0.4", default-features = false, features = ["alloc"] }

步骤 2:定义电路(以 SHA256 压缩函数 1 轮为例,门数 25 000)

use bellman::{Circuit, ConstraintSystem, SynthesisError};
use bls12_381::Scalar as Fr;
use bellman::gadgets::boolean::{AllocatedBit, Boolean};
use bellman::gadgets::sha256::sha256_compression_function;

pub struct Sha256Circuit {
    pub input: Option<[u8; 64]>,
}

impl Circuit<Fr> for Sha256Circuit {
    fn synthesize<CS: ConstraintSystem<Fr>>(
        self,
        cs: &mut CS,
    ) -> Result<(), SynthesisError> {
        // 分配 512 bit 输入
        let mut input_bits = Vec::with_capacity(512);
        for (i, byte) in self.input.iter().enumerate() {
            for j in 0..8 {
                let bit = (*byte >> j) & 1;
                let alloc = AllocatedBit::alloc(
                    cs.namespace(|| format!("input_{}_{}", i, j)),
                    Some(bit == 1),
                )?;
                input_bits.push(Boolean::Is(alloc));
            }
        }
        // 初始向量
        let h = [0u32; 8].map(|_| Some(Fr::from(0)));
        // 调用 bellman 提供的 SHA256 压缩函数
        let _output = sha256_compression_function(
            cs.namespace(|| "sha256"),
            &input_bits,
            &h.map(|f| Ok(f.unwrap())).to_vec(),
        )?;
        Ok(())
    }
}

步骤 3:可信设置(仅一次,必须在国密硬件加密机里跑

use bellman::groth16::{generate_random_parameters, Parameters};
use rand_core::OsRng;

let circuit = Sha256Circuit { input: None };
let params: Parameters<bls12_381::Bls12> =
    generate_random_parameters::<_, _, OsRng>(circuit, &mut OsRng)
        .expect("setup failed");
// 把 params.vk 写进区块链创世块,params.pk 用 AES-256-GCM 加密后存 IPFS,密钥放 HSM

步骤 4:生成证明(链下节点,内存监控脚本随时触发 OOM killer

use bellman::groth16::{create_random_proof, Proof};
use bls12_381::Bls12;

let circuit = Sha256Circuit {
    input: Some([0x61; 64]), // "aaaa..."
};
let proof: Proof<Bls12> =
    create_random_proof(circuit, &params.pk, &mut OsRng)
        .expect("prove failed");
let proof_bytes = proof.write(); // 192 字节,直接送交易

步骤 5:链上验证(内置在国产 FISCO-BCOS 预编译合约

use bellman::groth16::verify_proof;
use bls12_381::Scalar as Fr;

let public_inputs = [Fr::from(0)]; // SHA256 输出转 Fr
let ok = verify_proof(&params.vk, &proof, &public_inputs)
    .unwrap_or(false);
assert!(ok);

关键提醒

  • 整个流程 禁止用 std::fs 写临时文件,所有参数只走 内存映射 + AES 流解密,满足 《个人信息保护法》最小存储原则
  • 若业务需 批量证明,务必把 “witness 计算” 放 rayon parallel iterator,并把 bellman 的 multiexp 池化,可把 1000 个证明耗时从 230 s 降到 38 s(鲲鹏 920 2P 实测)。

拓展思考

  1. plonk 与 groth16 的选型
    国内央行数字货币桥项目已明确 “一次电路升级 = 一次 MPC” 不可接受,因此 plonkish 通用设置 更吃香;但若你所在团队做 固定算法、超高性能银联支付 Tokengroth16 + bellman 仍是唯一能把证明时间压到 7 ms 的方案
  2. 国密曲线替换
    把 bls12_381 换成 SM2 嵌入曲线 时,Fr 模数从 255 bit 降到 256 bit,需要重写 Fq、Fr 的 montgomery 汇编;目前 蚂蚁链的 zksm2 已开源,可对比 bellman 的 Engine trait 移植量。
  3. 硬件加速
    阿里平头哥 510 H 加速器 上,可把 multiexp 大数模乘 下放到 FPGA,bellman 只需暴露 GPU/FPGA 的统一 Device 抽象;面试时可反问面试官:
    “贵司若用 AWS f1 实例,是否考虑把 bellman 的 multiexp 换成 OpenCL kernel?我可以把 rust-opencl 与 bellman 的 SourceBuilder 做 zero-copy 映射。”
  4. 形式化验证
    国内监管已要求 “核心加密代码需通过国密局形式化审查”;bellman 的 gadget 层可用 kani 做内存安全验证,再用 coq-bellman(社区实验项目)做 电路语义等价证明。面试时提到这一点,可瞬间拉开与普通区块链 CRUD 工程师的差距。