零知识证明允许证明者向验证者证明某个陈述为真,且无需透露任何额外信息,bellman和libsnark是C++生态中两款成熟的零知识证明实现库,前者由Zcash团队开发,后者是较早开源的通用零知识证明库,两者都支持zk-SNARKs协议,可用于构建隐私保护类应用。

bellman库的使用方法
环境准备
bellman依赖Rust工具链,需要先安装Rust环境,之后通过Cargo管理依赖,若要在纯C++项目中调用,可通过FFI封装接口。首先安装Rust:
# 安装Rust工具链 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 配置环境变量 source $HOME/.cargo/env
基础电路定义与证明生成
bellman使用电路抽象来定义零知识证明的计算逻辑,以下是一个简单的算术电路示例,证明者证明自己知道两个数x和y,满足x*y=100:
// 引入bellman相关依赖
use bellman::{Circuit, ConstraintSystem, SynthesisError};
use bellman::gadgets::num::Num;
use pairing::bls12_381::{Bls12_381, Fr};
use rand::thread_rng;
// 定义电路结构
struct MultiplyCircuit {
x: Option<Fr>,
y: Option<Fr>,
}
// 实现Circuit trait定义电路约束
impl<'a> Circuit<Fr> for MultiplyCircuit {
fn synthesize<CS: ConstraintSystem<Fr>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
// 分配输入变量x
let x_val = Num::new(cs.namespace(|| "x"), self.x)?;
// 分配输入变量y
let y_val = Num::new(cs.namespace(|| "y"), self.y)?;
// 定义约束x*y=100,100转换为Fr类型
let target = Fr::from_str("100").unwrap();
let product = x_val.mul(cs.namespace(|| "x*y"), &y_val)?;
cs.enforce(
|| "x*y == 100",
|lc| lc + product.get_variable(),
|lc| lc + CS::one(),
|lc| lc + (target, CS::one())
);
Ok(())
}
}
fn main() {
// 初始化随机数生成器
let rng = &mut thread_rng();
// 生成公共参数
let params = {
let circuit = MultiplyCircuit { x: None, y: None };
bellman::groth16::generate_random_parameters::<Bls12_381, _, _>(circuit, rng).unwrap()
};
// 构造实际输入,x=10,y=10
let circuit = MultiplyCircuit {
x: Some(Fr::from_str("10").unwrap()),
y: Some(Fr::from_str("10").unwrap()),
};
// 生成证明
let proof = bellman::groth16::create_random_proof(circuit, ¶ms, rng).unwrap();
// 验证证明
let pvk = bellman::groth16::prepare_verifying_key(¶ms.vk);
let result = bellman::groth16::verify_proof(&pvk, &proof, &[]).unwrap();
assert!(result);
println!("bellman证明验证通过");
}
libsnark库的使用方法
环境准备
libsnark是纯C++实现的零知识证明库,需要先安装依赖库,包括libff、libfqfft等,可通过源码编译安装:
# 安装编译依赖 sudo apt-get install build-essential cmake git libgmp3-dev libboost-all-dev # 克隆libsnark源码 git clone https://github.com/scipr-lab/libsnark.git cd libsnark # 编译安装 mkdir build && cd build cmake .. make sudo make install
基础电路定义与证明生成
libsnark使用protoboard来定义电路,以下是同样实现x*y=100的示例:
#include <libsnark/common/default_types/r1cs_ppzksnark_pp.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp>
#include <libsnark/gadgetlib1/protoboard.hpp>
#include <libsnark/gadgetlib1/gadgets/basic_gadgets.hpp>
#include <iostream>
using namespace libsnark;
using namespace std;
// 定义乘法电路
template<typename FieldT>
class multiply_gadget : public gadget<FieldT> {
private:
pb_variable<FieldT> x;
pb_variable<FieldT> y;
pb_variable<FieldT> product;
public:
multiply_gadget(protoboard<FieldT> &pb, const pb_variable<FieldT> &x_var, const pb_variable<FieldT> &y_var, const pb_variable<FieldT> &prod_var) :
gadget<FieldT>(pb), x(x_var), y(y_var), product(prod_var) {}
void generate_r1cs_constraints() {
// 添加约束x*y=product
this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(x, y, product));
}
void generate_r1cs_witness(const FieldT &x_val, const FieldT &y_val) {
this->pb.val(x) = x_val;
this->pb.val(y) = y_val;
this->pb.val(product) = x_val * y_val;
}
};
int main() {
// 初始化曲线参数
default_r1cs_ppzksnark_pp::init_public_params();
typedef Fr<default_r1cs_ppzksnark_pp> FieldT;
// 创建protoboard
protoboard<FieldT> pb;
// 分配变量
pb_variable<FieldT> x, y, product;
x.allocate(pb, "x");
y.allocate(pb, "y");
product.allocate(pb, "product");
// 设置目标值100对应的域元素
FieldT target = FieldT::from_string("100");
// 添加约束product=100
pb.add_r1cs_constraint(r1cs_constraint<FieldT>(product, FieldT::one(), target));
// 初始化乘法电路
multiply_gadget<FieldT> mul_gadget(pb, x, y, product);
mul_gadget.generate_r1cs_constraints();
// 生成公共参数
auto r1cs_constraint_system = pb.get_constraint_system();
auto keypair = r1cs_ppzksnark_generator<default_r1cs_ppzksnark_pp>(r1cs_constraint_system);
// 设置见证值x=10,y=10
mul_gadget.generate_r1cs_witness(FieldT::from_string("10"), FieldT::from_string("10"));
// 生成证明
auto proof = r1cs_ppzksnark_prover<default_r1cs_ppzksnark_pp>(keypair.pk, pb.primary_input(), pb.auxiliary_input());
// 验证证明
bool result = r1cs_ppzksnark_verifier_strong_IC<default_r1cs_ppzksnark_pp>(keypair.vk, pb.primary_input(), proof);
if (result) {
cout << "libsnark证明验证通过" << endl;
} else {
cout << "libsnark证明验证失败" << endl;
}
return 0;
}
两个库的对比与选择
| 对比维度 | bellman | libsnark |
|---|---|---|
| 开发语言 | Rust为主,可封装C++接口 | 纯C++ |
| 默认曲线 | BLS12-381 | 支持多种曲线,默认BN128 |
| 易用性 | 抽象层次更高,代码更简洁 | 底层接口更丰富,配置更灵活 |
| 适用场景 | 区块链隐私交易、轻量隐私应用 | 复杂电路定制、学术研究与底层开发 |
常见问题说明
- bellman的Rust代码若要在C++项目中调用,需要编写FFI接口,将Rust函数导出为C兼容的函数符号,再通过C++链接调用。
- libsnark编译时若提示依赖缺失,需要检查是否安装了libgmp和boost库,不同系统的安装命令略有差异。
- 两个库生成的证明都是zk-SNARKs类型,证明体积小验证速度快,适合对性能要求高的场景。