本文介绍了使用Rust编程语言和Bellman库实现Groth16零知识证明,并测量了生成和验证证明所需的时间。实验结果表明,生成电路需要较长时间(140秒),生成证明需要15秒,但验证证明非常快(0.03秒)。

一根绳子有多长?嗯,谁知道呢!但是,创建一个和验证一个零知识证明需要多长时间?嗯,这是我们可以实际测量的事情。我们将发现,我们可能需要大约两分钟来生成证明,但我们可以快速检查证明是否正确。总的来说,生成 ZKP 可能需要一段时间,但之后验证起来很快。
由于我们想要良好的性能,我们将使用 Rust 编程语言来实现这一点,它将被编译成一个可执行程序。一个常见的 ZKP 是 Groth16,它由 Jen Groth 在 2016 年创建 [1]。它使用基于配对的密码学,并创建一个简短的证明。最好的 Rust 库之一是 Bellman 库。
以下代码基于这里:
use bellman::{
gadgets::{
boolean::{AllocatedBit, Boolean},
multipack,
sha256::sha256,
},
groth16, Circuit, ConstraintSystem, SynthesisError,
};
use bls12_381::Bls12;
use ff::PrimeField;
use rand::rngs::OsRng;
use sha2::{Digest, Sha256};
use std::env;
/// 我们自己的 SHA-256d gadget。输入和输出都采用小端位顺序。
fn sha256d<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
mut cs: CS,
data: &[Boolean],
) -> Result<Vec<Boolean>, SynthesisError> {
// 翻转每个输入字节的字节序
let input: Vec<_> = data
.chunks(8)
.map(|c| c.iter().rev())
.flatten()
.cloned()
.collect();
let mid = sha256(cs.namespace(|| "SHA-256(input)"), &input)?;
let res = sha256(cs.namespace(|| "SHA-256(mid)"), &mid)?;
// 翻转每个输出字节的字节序
Ok(res
.chunks(8)
.map(|c| c.iter().rev())
.flatten()
.cloned()
.collect())
}
struct MyCircuit {
/// 我们正在证明我们知道的 SHA-256d 的输入。当我们验证证明(并且没有 witness 数据)时,设置为 `None`。
preimage: Option<[u8; 80]>,
}
impl<Scalar: PrimeField> Circuit<Scalar> for MyCircuit {
fn synthesize<CS: ConstraintSystem<Scalar>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
// 计算 preimage 各位的值。如果我们正在验证证明,我们仍然需要创建相同的约束,因此我们返回一个等效大小的 Vec<None>(表示每个位的值未知)。
let bit_values = if let Some(preimage) = self.preimage {
preimage
.into_iter()
.map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
.flatten()
.map(|b| Some(b))
.collect()
} else {
vec![None; 80 * 8]
};
assert_eq!(bit_values.len(), 80 * 8);
// Witness preimage 的各位。
let preimage_bits = bit_values
.into_iter()
.enumerate()
// 分配每个位。
.map(|(i, b)| {
AllocatedBit::alloc(cs.namespace(|| format!("preimage bit {}", i)), b)
})
// 将 AllocatedBits 转换为 Booleans(sha256 gadget 需要)。
.map(|b| b.map(Boolean::from))
.collect::<Result<Vec<_>, _>>()?;
// 计算 hash = SHA-256d(preimage)。
let hash = sha256d(cs.namespace(|| "SHA-256d(preimage)"), &preimage_bits)?;
// 将 32 个布尔变量的向量公开为紧凑的公共输入。
multipack::pack_into_inputs(cs.namespace(|| "pack hash"), &hash)
}
}
fn main() {
// 为我们的电路创建参数。在生产部署中,这些将使用多方计算安全地生成。
let mut msg="Hello";
let args: Vec < String > = env::args().collect();
if args.len() >1 { msg = args[1].as_str();}
let mut rng = rand::thread_rng();
use std::time::Instant;
let now = Instant::now();
println!("\nSetting up");
let params = {
let c = MyCircuit { preimage: None };
groth16::generate_random_parameters::<Bls12, _, _>(c, &mut rng).unwrap()
};
println!("\n== Setting up verification key");
// 准备验证密钥(用于证明验证)。
let pvk = groth16::prepare_verifying_key(¶ms.vk);
println!("\n== Verifying key created");
let elapsed = now.elapsed();
println!("Elapsed: {:.2?}", elapsed);
// 选择一个 preimage 并计算其哈希值。
let mut preimage = [0; 80];
// let plain=msg.as_bytes();
let mut n=0;
for ch in msg.chars() {
preimage[n]= ch as u8;
n=n+1;
}
let hash = Sha256::digest(&Sha256::digest(&preimage));
println!("Message to prove is {0}\n",msg);
println!("Hash is {0}\n",&hex::encode(hash));
// 创建电路的一个实例(以 preimage 作为 witness)。
let c = MyCircuit {
preimage: Some(preimage),
};
println!("== Creating proof");
// 使用我们的参数创建一个 Groth16 证明。
let proof = groth16::create_random_proof(c, ¶ms, &mut OsRng).unwrap();
let elapsed = now.elapsed();
println!("Elapsed: {:.2?}", elapsed);
// 将哈希值打包为证明验证的输入。
let hash_bits = multipack::bytes_to_bits_le(&hash);
let inputs = multipack::compute_multipacking(&hash_bits);
// 检查证明!
let ver=groth16::verify_proof(&pvk, &proof, &inputs);
if ver.is_ok() { println!("\nProof verified"); }
else { println!("\nProof NOT verified"); }
let elapsed = now.elapsed();
println!("Elapsed: {:.2?}", elapsed);
}
为此,我们设置一个 pre-image,它将使用 SHA-256 进行哈希以用于证明。 pre-image 包含 80 字节的信息, 这些信息使用 NULL 字符 (0) 初始化为这 80 个字节。然后,我们将字符串中的字符添加到 pre-image:
let mut preimage = [0; 80];
let mut n=0;
for ch in msg.chars() {
preimage[n]= ch as u8;
n=n+1;
}
然后我们可以获取此 pre-image 的 SHA-256:
let hash = Sha256::digest(&Sha256::digest(&preimage));
然后使用它来创建证明:
println!("== Creating proof");
// 使用我们的参数创建一个 Groth16 证明。
let proof = groth16::create_random_proof(c, ¶ms, &mut OsRng).unwrap();
该电路是使用变量 c 创建的,并且是从 pre-image 的 SHA-256 哈希创建的。因此,此证明将证明 pre-image 的知识。最后,我们可以使用以下方法进行验证:
// 将哈希值打包为证明验证的输入。
let hash_bits = multipack::bytes_to_bits_le(&hash);
let inputs = multipack::compute_multipacking(&hash_bits);
// 检查证明!
let ver=groth16::verify_proof(&pvk, &proof, &inputs);
if ver.is_ok() { println!("\nProof verified"); }
else { println!("\nProof NOT verified"); }
当我们使用字符串“Test123”运行时,我们得到:
Setting up circuit
Elapsed: 140.46s
== Setting up verification key
== Verifying key created
Elapsed: 140.49s
Message to prove is Test123
Hash is 15d6efed64a87bbd0679700dd2fe22f0c23c3a6b9a4c80ee42ae965e1dff66e7
== Creating proof
Elapsed: 155.32s
Proof verified
Elapsed: 155.35s
因此,我们看到生成电路花费了将近 140 秒, 然后生成证明花费了大约 15 秒, 但证明它只花费了 0.03 秒。
就这样,生成初始电路可能需要相当长的时间,但创建证明的速度更快。但是,可以快速完成证明的验证。如果你想了解有关 Jen Groth 及其方法的更多信息,请尝试以下方法:
[1] Groth, J. (2016, April). On the size of pairing-based non-interactive arguments. In Annual international conference on the theory and applications of cryptographic techniques (pp. 305–326). Berlin, Heidelberg: Springer Berlin Heidelberg.
- 原文链接: billatnapier.medium.com/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!