Skip to main content

Groth16

零知识证明允许证明者验证一个陈述是否为真,而无需透露有关输入的任何信息。例如,证明者可以验证他们知道数独谜题的解决方案,而不透露解决方案。

零知识简洁非交互知识论证(zk-SNARKs)是一类零知识证明,它们是非交互式的,具有简洁的证明大小和高效的验证时间。其中一个重要且广泛使用的变体是基于配对的 zk-SNARKs,例如 Groth16 证明系统,它是最高效且广泛使用的之一。

Sui 中的 Move API 使你能够使用 Groth16 zk-SNARKs 在 BN254 或 BLS12-381 椭圆曲线构造上高效验证可以用 NP 完全语言表示的任何陈述。

有一些用于表达这些陈述的高级语言,例如 Circom,在以下示例中使用。

Groth16 需要为每个电路进行可信设置,以生成验证密钥。API 不钉住任何特定的验证密钥,每个用户都可以为其应用程序生成自己的参数或使用现有的验证密钥。

用法

以下示例演示了如何从 Circom 中编写的陈述中创建 Groth16 证明,然后使用 Sui Move API 进行验证。API 当前支持最多八个公共输入。

创建电路

该证明表明我们知道哈希函数的秘密输入,该输入给出某个公共输出。

pragma circom 2.1.5;

include "node_modules/circomlib/circuits/poseidon.circom";

template Main() {
component poseidon = Poseidon(1);
signal input in;
signal output digest;
poseidon.inputs[0] <== in;
digest <== poseidon.out;
}

component main = Main();

我们使用 Poseidon 哈希函数,这是一种 ZK-friendly 的哈希函数。假设已经安装了 circom 编译器,可以使用以下命令编译上述电路:

circom main.circom --r1cs --wasm

这将输出 R1CS 格式的约束和 Wasm 格式的电路。

生成证明

要生成在 Sui 中可验证的证明,你需要生成一个见证。此示例使用 Arkworks 的 ark-circom Rust 库。该代码为电路构建了一个见证,并为给定的输入生成了一个证明。最后,它验证了证明的正确性。

use ark_bn254::Bn254;
use ark_circom::CircomBuilder;
use ark_circom::CircomConfig;
use ark_groth16::Groth16;
use ark_snark::SNARK;

fn main() {
// Load the WASM and R1CS for witness and proof generation
let cfg = CircomConfig::<Bn254>::new("main.wasm", "main.r1cs").unwrap();

// Insert our secret inputs as key value pairs. We insert a single input, namely the input to the hash function.
let mut builder = CircomBuilder::new(cfg);
builder.push_input("in", 7);

// Create an empty instance for setting it up
let circom = builder.setup();

// WARNING: The code below is just for debugging, and should instead use a verification key generated from a trusted setup.
// See for example https://docs.circom.io/getting-started/proving-circuits/#powers-of-tau.
let mut rng = rand::thread_rng();
let params =
Groth16::<Bn254>::generate_random_parameters_with_reduction(circom, &mut rng).unwrap();

let circom = builder.build().unwrap();

// There's only one public input, namely the hash digest.
let inputs = circom.get_public_inputs().unwrap();

// Generate the proof
let proof = Groth16::<Bn254>::prove(&params, circom, &mut rng).unwrap();

// Check that the proof is valid
let pvk = Groth16::<Bn254>::process_vk(&params.vk).unwrap();
let verified = Groth16::<Bn254>::verify_with_processed_vk(&pvk, &inputs, &proof).unwrap();
assert!(verified);
}

验证证明的 Sui API 需要一个特殊处理过的验证密钥,其中仅使用值的子集。理想情况下,此准备好的验证密钥的计算每个电路只需进行一次。你可以使用 Sui Move API 的 sui::groth16::prepare_verifying_key 方法,使用先前用过的 params.vk 值的序列化进行此处理。

prepare_verifying_key 函数的输出是一个包含四个字节数组的向量,对应于 vk_gamma_abc_g1_bytesalpha_g1_beta_g2_bytesgamma_g2_neg_pc_bytesdelta_g2_neg_pc_bytes

要验证证明,你还需要两个输入,即 proof_inputs_bytesproof_points_bytes,其中包含公共输入和证明。这些是从先前示例的 inputsproof 值计算的序列化,你可以在 Rust 中进行如下计算:

let mut proof_inputs_bytes = Vec::new();
inputs.serialize_compressed(&mut proof_inputs_bytes).unwrap();

let mut proof_points_bytes = Vec::new();
proof.a.serialize_compressed(&mut proof_points_bytes).unwrap();
proof.b.serialize_compressed(&mut proof_points_bytes).unwrap();
proof.c.serialize_compressed(&mut proof_points_bytes).unwrap();

以下示例智能合约准备了一个验证密钥并验证相应的证明。此示例使用 BN254 椭圆曲线构造,该构造作为 prepare_verifying_keyverify_groth16_proof 函数的第一个参数给出。对于 BLS12-381 构造,你可以改用 bls12381 函数。

module test::groth16_test {
use sui::groth16;
use sui::event;

/// Event on whether the proof is verified
struct VerifiedEvent has copy, drop {
is_verified: bool,
}

public fun verify_proof(vk: vector<u8>, public_inputs_bytes: vector<u8>, proof_points_bytes: vector<u8>) {
let pvk = groth16::prepare_verifying_key(&groth16::bn254(), &vk);
let public_inputs = groth16::public_proof_inputs_from_bytes(public_inputs_bytes);
let proof_points = groth16::proof_points_from_bytes(proof_points_bytes);
event::emit(VerifiedEvent {is_verified: groth16::verify_groth16_proof(&groth16::bn254(), &pvk, &public_inputs, &proof_points)});
}
}