Bằng chứng không kiến thức trong Rust & Circom: Xác minh SnarkJS & Hướng dẫn sản xuất

Mục lục bài viết(14 mục)
Zero-Knowledge Proofs (ZKP) cho phép một bên (người chứng minh) thuyết phục một bên khác (người xác minh) rằng một tuyên bố là đúng, mà không tiết lộ bất kỳ thông tin nào ngoài tính hợp lệ của chính tuyên bố đó. Hướng dẫn này trình bày chi tiết việc triển khai ZKP trong thực tế bằng cách sử dụng Circom để định nghĩa mạch, Rust với arkworks để tạo bằng chứng và SnarkJS để xác minh, bao gồm cả việc tích hợp với Solidity.
Hệ thống Hiệu năng cao & Cơ sở dữ liệu Hiện đại
Các Nguyên Tắc Cơ Bản & Kiến Trúc ZKP
Về cốt lõi, một hệ thống ZKP bao gồm:
- Định nghĩa Tuyên bố: Một tuyên bố toán học cần được chứng minh.
- Thiết kế Mạch: Chuyển đổi tuyên bố thành một mạch số học.
- Tạo Witness: Tính toán các đầu vào riêng tư (witness) thỏa mãn mạch.
- Tạo Bằng chứng: Tạo một bằng chứng mật mã dựa trên mạch và witness.
- Xác minh Bằng chứng: Kiểm tra tính hợp lệ của bằng chứng so với các đầu vào công khai.
Chúng ta sẽ tập trung vào zk-SNARKs (Zero-Knowledge Succinct Non-Interactive Argument of Knowledge), cụ thể là Groth16, vì hiệu quả và mức độ phổ biến rộng rãi của nó.
Tổng quan Kiến trúc
Kiến trúc sản xuất của chúng tôi bao gồm:
- Circom: Ngôn ngữ chuyên biệt (DSL) để định nghĩa các mạch số học. Biên dịch thành R1CS (Rank-1 Constraint System) và WASM để tạo witness.
- Rust (
arkworks): Thư viện hiệu suất cao cho các nguyên thủy mật mã, được sử dụng để tạo bằng chứng Groth16. Điều này có thể chạy trong một microservice chuyên dụng. - SnarkJS: Thư viện JavaScript để tạo witness (sử dụng đầu ra WASM từ Circom) và xác minh bằng chứng (cả off-chain và on-chain thông qua các hợp đồng xác minh Solidity).
- Solidity: Ngôn ngữ hợp đồng thông minh để xác minh bằng chứng on-chain.
Thiết kế Mạch với Circom
Circom là một DSL để định nghĩa các mạch số học. Nó biên dịch thành biểu diễn R1CS, là một tập hợp các phương trình bậc hai trên một trường hữu hạn.
Ví dụ: Mạch Chứng minh Danh tính (identity.circom)
Mạch này chứng minh kiến thức về một đầu vào bí mật x sao cho x bằng một đầu vào công khai identity_hash. Đây là một bằng chứng danh tính được đơn giản hóa.
pragma circom 2.1.5;
template IdentityProof() {
signal input x; // Private input
signal input identity_hash; // Public input
// Constraint: x must equal identity_hash
x === identity_hash;
}
component main = IdentityProof();
Ví dụ: Mạch Chứng minh Phạm vi (range.circom)
Mạch này chứng minh rằng một đầu vào bí mật x nằm trong một phạm vi cụ thể [min, max] mà không tiết lộ x. Chúng ta sẽ sử dụng phương pháp phân tách bit.
pragma circom 2.1.5;
// Helper component to check if a number is within a range [0, N-1]
// by decomposing it into bits.
template Num2Bits(n) {
signal input in;
signal output out[n];
var sum = 0;
for (var i = 0; i < n; i++) {
out[i] <-- (in >> i) & 1; // Extract i-th bit
out[i] * (1 - out[i]) === 0; // Constraint: bit must be 0 or 1
sum += out[i] * (1 << i);
}
sum === in; // Constraint: sum of bits must equal input
}
template RangeProof(n_bits, min_val, max_val) {
signal input x; // Private input
signal input public_min; // Public input (for flexibility, can be hardcoded)
signal input public_max; // Public input (for flexibility, can be hardcoded)
// Ensure x is non-negative (if range starts from 0)
// For simplicity, we assume x >= 0. If negative numbers are possible,
// a more complex range check is needed.
// Check x >= public_min
signal diff_min;
diff_min <== x - public_min;
component bits_diff_min = Num2Bits(n_bits); // n_bits should be sufficient for max_val - min_val
bits_diff_min.in <== diff_min;
// Check x <= public_max
signal diff_max;
diff_max <== public_max - x;
component bits_diff_max = Num2Bits(n_bits); // n_bits should be sufficient for max_val - min_val
bits_diff_max.in <== diff_max;
// Public inputs must match the hardcoded range for this specific proof instance
public_min === min_val;
public_max === max_val;
}
// Example: Prove x is in range [10, 100] using 8 bits (max value 2^8 - 1 = 255)
component main = RangeProof(8, 10, 100);
Biên dịch Mạch Circom
Để biên dịch các mạch này, bạn cần cài đặt circom.
# Install circom if not already installed
# npm install -g circom_tester # or yarn global add circom_tester
# Compile IdentityProof
circom identity.circom --r1cs --wasm --sym --json
# Compile RangeProof
circom range.circom --r1cs --wasm --sym --json
Điều này tạo ra:
.r1cs: Hệ thống ràng buộc R1CS._js/: Thư mục chứawitness_calculator.jsvà*.wasmđể tạo witness..sym: Các ký hiệu gỡ lỗi..json: Thông tin mạch.
Thiết lập Tin cậy
Groth16 yêu cầu một thiết lập tin cậy. Đối với sản xuất, hãy sử dụng nghi thức tính toán đa bên (MPC). Đối với phát triển, snarkjs có thể tạo thiết lập giai đoạn 1.
# Generate a new trusted setup (powers of tau)
snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
# Contribute to the setup (simulated for dev)
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v
# Apply the circuit-specific phase 2 setup for IdentityProof
snarkjs groth16 setup identity.r1cs pot12_0001.ptau identity_0000.zkey
# Contribute to the phase 2 setup (simulated for dev)
snarkjs zkey contribute identity_0000.zkey identity_final.zkey --name="Second contribution" -v
# Export verification key
snarkjs zkey export verificationkey identity_final.zkey verification_key_identity.json
# Repeat for RangeProof
snarkjs groth16 setup range.r1cs pot12_0001.ptau range_0000.zkey
snarkjs zkey contribute range_0000.zkey range_final.zkey --name="Range contribution" -v
snarkjs zkey export verificationkey range_final.zkey verification_key_range.json
Các tệp identity_final.zkey và range_final.zkey chứa khóa chứng minh, và verification_key_identity.json / verification_key_range.json chứa khóa xác minh.
Tạo Witness với SnarkJS
Việc tạo witness thường được thực hiện ở phía client hoặc trong một dịch vụ chuyên dụng. snarkjs cung cấp API JavaScript cho việc này.
// witness_generator.ts
import { WitnessCalculator } from 'circom_runtime';
import * as fs from 'fs';
import * as path from 'path';
async function generateWitness(circuitName: string, inputs: Record<string, any>): Promise<any> {
const wasmPath = path.join(__dirname, `${circuitName}_js`, `${circuitName}.wasm`);
const witnessCalculator = await WitnessCalculator(fs.readFileSync(wasmPath));
const fullWitness = await witnessCalculator.calculateWitness(inputs, true);
// The first element is the signal `1`, subsequent elements are public inputs, then private inputs.
// We usually only need the full witness for proof generation.
return fullWitness;
}
// Example usage for IdentityProof
async function generateIdentityWitness() {
const privateX = 12345;
const publicIdentityHash = 12345; // Must match privateX for a valid proof
const inputs = {
x: privateX,
identity_hash: publicIdentityHash,
};
console.log(`Generating witness for IdentityProof with inputs: ${JSON.stringify(inputs)}`);
const witness = await generateWitness('identity', inputs);
console.log('IdentityProof Witness generated.');
// In a real scenario, you might serialize this witness or pass it directly.
// For arkworks, we need the public inputs and the full witness.
// The arkworks prover expects a specific format, typically a vector of field elements.
// The witness array from snarkjs is already field elements.
return { witness, publicInputs: [publicIdentityHash] };
}
// Example usage for RangeProof
async function generateRangeWitness() {
const privateX = 50;
const publicMin = 10;
const publicMax = 100;
const inputs = {
x: privateX,
public_min: publicMin,
public_max: publicMax,
};
console.log(`Generating witness for RangeProof with inputs: ${JSON.stringify(inputs)}`);
const witness = await generateWitness('range', inputs);
console.log('RangeProof Witness generated.');
return { witness, publicInputs: [publicMin, publicMax] };
}
// To run:
// ts-node witness_generator.ts
// (Ensure circom_runtime is installed: npm install circom_runtime)
Tạo Bằng chứng với Rust (arkworks)
Rust với arkworks cung cấp một môi trường mạnh mẽ và hiệu quả cho các hoạt động ZKP. Chúng ta sẽ sử dụng ark-circom để tải R1CS và ark-groth16 để tạo bằng chứng.
Đầu tiên, thiết lập dự án Rust của bạn:
cargo new --bin zkp_prover
cd zkp_prover
Thêm các dependency vào Cargo.toml:
[package]
name = "zkp_prover"
version = "0.1.0"
edition = "2021"
[dependencies]
ark-bn254 = { version = "0.4.0", features = ["curve"] }
ark-circom = "0.4.0"
ark-ff = "0.4.0"
ark-groth16 = "0.4.0"
ark-relations = "0.4.0"
ark-std = { version = "0.4.0", features = ["print-trace"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
hex = "0.4"
Bây giờ, mã Rust để tạo bằng chứng. Ví dụ này giả định các tệp identity_final.zkey và identity.r1cs có thể truy cập được.
// src/main.rs
use ark_bn254::{Bn254, Fr};
use ark_circom::{CircomBuilder, CircomCircuit, R1CS};
use ark_ff::{BigInt, PrimeField};
use ark_groth16::{
create_random_proof, generate_random_parameters, prepare_verifying_key, verify_proof, Proof,
};
use ark_std::{rand::thread_rng, UniformRand};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fs::File, io::BufReader, path::PathBuf};
// Structure to hold the proof data for serialization
#[derive(Serialize, Deserialize, Debug)]
pub struct Groth16Proof {
pub a: Vec<String>, // G1 point (x, y)
pub b: Vec<Vec<String>>, // G2 point (x[0], x[1], y[0], y[1])
pub c: Vec<String>, // G1 point (x, y)
}
impl From<Proof<Bn254>> for Groth16Proof {
fn from(proof: Proof<Bn254>) -> Self {
Self {
a: vec![
format!("{:?}", proof.a.x),
format!("{:?}", proof.a.y),
],
b: vec![
vec![
format!("{:?}", proof.b.x.c0),
format!("{:?}", proof.b.x.c1),
],
vec![
format!("{:?}", proof.b.y.c0),
format!("{:?}", proof.b.y.c1),
],
],
c: vec![
format!("{:?}", proof.c.x),
format!("{:?}", proof.c.y),
],
}
}
}
// Helper to convert field elements to BigInt for CircomBuilder
fn field_to_bigint<F: PrimeField>(f: F) -> BigInt<4> {
f.into_bigint()
}
async fn generate_identity_proof(
r1cs_path: PathBuf,
zkey_path: PathBuf,
private_x: u64,
public_identity_hash: u64,
) -> Result<(Groth16Proof, Vec<Fr>), Box<dyn std::error::Error>> {
let mut rng = thread_rng();
// 1. Load R1CS
let r1cs = R1CS::from_file(r1cs_path)?;
// 2. Create CircomBuilder and assign inputs
let mut builder = CircomBuilder::new(r1cs);
builder.push_input("x", private_x);
builder.push_input("identity_hash", public_identity_hash);
// 3. Build the circuit and generate the witness
let circuit = builder.setup();
let full_assignments = circuit.generate_witness()?;
// Extract public inputs from the full witness
// The first element is always 1, then public inputs, then private inputs.
// For IdentityProof, public_identity_hash is the only public input.
let public_inputs = vec![full_assignments[1]]; // Assuming identity_hash is the first public signal after 1
// 4. Load proving key from zkey file
// In a real scenario, you'd load the proving key directly, not generate it.
// For simplicity, we'll generate parameters here using the R1CS.
// For production, the `zkey_path` would contain the pre-computed proving key.
// ark-circom's `CircomBuilder` can directly load the proving key from a zkey.
// However, `ark-groth16` expects `ProvingKey`.
// A more direct way to load from zkey is complex due to `ark-circom`'s current API.
// For this example, we'll generate parameters from R1CS, which is NOT production-ready.
// Production: Load PK from `zkey_path` (e.g., using `snarkjs zkey export json` and parsing).
let params = generate_random_parameters::<Bn254, _, _>(circuit.clone(), &mut rng)?;
// 5. Create the proof
let proof = create_random_proof(circuit, params, &mut rng)?;
Ok((proof.into(), public_inputs))
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Paths to your compiled Circom artifacts and zkey
let r1cs_path = PathBuf::from("./identity.r1cs");
let zkey_path = PathBuf::from("./identity_final.zkey"); // This is the proving key
let private_x = 12345;
let public_identity_hash = 12345;
println!("Generating IdentityProof...");
let (proof, public_inputs) = generate_identity_proof(
r1cs_path,
zkey_path,
private_x,
public_identity_hash,
).await?;
println!("Proof generated: {:?}", proof);
println!("Public inputs: {:?}", public_inputs);
// For verification, you would typically send `proof` and `public_inputs` to a verifier.
// Here, we'll demonstrate local verification using ark-groth16.
// In production, the verification key would be loaded from `verification_key_identity.json`.
// Load verification key (for local verification demonstration)
let vk_file = File::open("./verification_key_identity.json")?;
let vk_json: serde_json::Value = serde_json::from_reader(BufReader::new(vk_file))?;
// Parse the verification key from JSON into ark-groth16's `VerifyingKey` struct.
// This parsing is non-trivial and requires careful mapping of JSON fields to struct fields.
// For simplicity, we'll re-generate VK from params for local verification.
// Production: Parse `verification_key_identity.json` into `ark_groth16::VerifyingKey<Bn254>`.
// This is a complex step due to the structure of `ark-groth16`'s `VerifyingKey` and `snarkjs`'s JSON output.
// A common approach is to use `snarkjs zkey export solidityverifier` and then use the generated Solidity contract.
// Or, write a custom parser for the `verification_key_identity.json` into `ark_groth16::VerifyingKey`.
// For this example, we'll use the `params` generated earlier to get the VK.
// This means the local verification uses the same setup as proof generation, which is not how production works.
// In production, the VK is fixed after trusted setup.
let r1cs_for_vk = R1CS::from_file(PathBuf::from("./identity.r1cs"))?;
let circuit_for_vk = CircomBuilder::new(r1cs_for_vk).setup();
let params_for_vk = generate_random_parameters::<Bn254, _, _>(circuit_for_vk, &mut rng)?;
let pvk = prepare_verifying_key(¶ms_for_vk.vk);
// Convert the generated proof back to ark-groth16's Proof struct for local verification
let ark_proof = Proof {
a: ark_bn254::G1Affine::new(
Fr::from_str_radix(&proof.a[0].trim_start_matches("0x"), 16)?,
Fr::from_str_radix(&proof.a[1].trim_start_matches("0x"), 16)?,
),
b: ark_bn254::G2Affine::new(
ark_bn254::Fq2::new(
Fr::from_str_radix(&proof.b[0][0].trim_start_matches("0x"), 16)?,
Fr::from_str_radix(&proof.b[0][1].trim_start_matches("0x"), 16)?,
),
ark_bn254::Fq2::new(
Fr::from_str_radix(&proof.b[1][0].trim_start_matches("0x"), 16)?,
Fr::from_str_radix(&proof.b[1][1].trim_start_matches("0x"), 16)?,
),
),
c: ark_bn254::G1Affine::new(
Fr::from_str_radix(&proof.c[0].trim_start_matches("0x"), 16)?,
Fr::from_str_radix(&proof.c[1].trim_start_matches("0x"), 16)?,
),
};
let is_valid = verify_proof(&pvk, &ark_proof, &public_inputs)?;
println!("Local verification result: {}", is_valid);
Ok(())
}
Lưu ý quan trọng về Rust Prover: Lời gọi generate_random_parameters trong ví dụ Rust là để minh họa và kiểm thử cục bộ. Trong môi trường sản xuất, khóa chứng minh (PK) sẽ được tải từ zkey_path được tạo trong quá trình thiết lập tin cậy, chứ không phải tạo lại. Việc tải ProvingKey từ một tệp snarkjs .zkey trực tiếp vào ark-groth16 không hề đơn giản do sự khác biệt về định dạng. Một cách giải quyết phổ biến là sử dụng snarkjs zkey export json và sau đó phân tích cú pháp JSON đó thành cấu trúc ark-groth16's ProvingKey, hoặc sử dụng ark-circom's CircomProver xử lý việc tải zkey. Cấu trúc Groth16Proof được cung cấp là để tuần tự hóa bằng chứng ark-groth16 thành một định dạng tương thích với SnarkJS/Solidity.
Xác minh Bằng chứng với SnarkJS
SnarkJS có thể xác minh bằng chứng off-chain bằng cách sử dụng verification_key.json và bằng chứng đã tạo.
// verifier.ts
import * as snarkjs from 'snarkjs';
import * as fs from 'fs';
import * as path from 'path';
async function verifyProof(
circuitName: string,
publicInputs: any[],
proof: any
): Promise<boolean> {
const vKeyPath = path.join(__dirname, `verification_key_${circuitName}.json`);
const vKey = JSON.parse(fs.readFileSync(vKeyPath, 'utf-8'));
const res = await snarkjs.groth16.verify(vKey, publicInputs, proof);
return res;
}
// Example usage (assuming you have a proof and public inputs from the Rust prover)
async function runVerification() {
// These would typically come from the Rust prover service
const identityProof = {
pi_a: ["0x...", "0x...", "0x..."], // G1 point
pi_b: [["0x...", "0x..."], ["0x...", "0x..."]], // G2 point
pi_c: ["0x...", "0x...", "0x..."] // G1 point
};
const identityPublicInputs = ["12345"];
console.log('Verifying IdentityProof...');
const isValidIdentity = await verifyProof('identity', identityPublicInputs, identityProof);
console.log(`IdentityProof verification result: ${isValidIdentity}`);
// Example for RangeProof
const rangeProof = { /* ... */ };
const rangePublicInputs = ["10", "100"];
console.log('Verifying RangeProof...');
const isValidRange = await verifyProof('range', rangePublicInputs, rangeProof);
console.log(`RangeProof verification result: ${isValidRange}`);
}
// To run:
// ts-node verifier.ts
// (Ensure snarkjs is installed: npm install snarkjs)
Cấu trúc Groth16Proof trong Rust được thiết kế để tuần tự hóa thành một định dạng mà snarkjs.groth16.verify mong đợi. Các trường pi_a, pi_b, pi_c trong SnarkJS tương ứng với a, b, c trong ark-groth16.
Xác minh On-Chain với Solidity
Để xác minh on-chain, snarkjs có thể tạo một hợp đồng xác minh Solidity.
# Export Solidity verifier for IdentityProof
snarkjs zkey export solidityverifier identity_final.zkey verifier_identity.sol
# Export Solidity verifier for RangeProof
snarkjs zkey export solidityverifier range_final.zkey verifier_range.sol
Tệp verifier_identity.sol được tạo sẽ chứa một hàm verifyProof.
// verifier_identity.sol (simplified)
pragma solidity ^0.8.0;
contract Verifier {
function verifyProof(
uint[2] memory _pA,
uint[2][2] memory _pB,
uint[2] memory _pC,
uint[1] memory _pubSignals
) public view returns (bool) {
// ... cryptographic verification logic ...
// This function will return true if the proof is valid for the given public signals.
}
}
Để gọi hàm này từ một DApp, bạn sẽ truyền các thành phần bằng chứng (pi_a, pi_b, pi_c) và các tín hiệu công khai từ dịch vụ frontend hoặc backend của bạn.
// web3_verifier.ts (example using ethers.js)
import { ethers } from 'ethers';
import * as fs from 'fs';
import * as path from 'path';
// Assuming you have a deployed Verifier contract
const VERIFIER_CONTRACT_ADDRESS = "0x..."; // Replace with your deployed contract address
const VERIFIER_ABI = JSON.parse(fs.readFileSync(path.join(__dirname, 'Verifier_abi.json'), 'utf-8'));
async function verifyOnChain(
provider: ethers.Provider,
proof: any, // SnarkJS proof format
publicInputs: string[]
): Promise<boolean> {
const verifier = new ethers.Contract(VERIFIER_CONTRACT_ADDRESS, VERIFIER_ABI, provider);
// Convert SnarkJS proof format to Solidity-compatible format
const pA: [string, string] = [proof.pi_a[0], proof.pi_a[1]];
const pB: [[string, string], [string, string]] = [
[proof.pi_b[0][0], proof.pi_b[0][1]],
[proof.pi_b[1][0], proof.pi_b[1][1]]
];
const pC: [string, string] = [proof.pi_c[0], proof.pi_c[1]];
// Public signals need to be `uint` in Solidity, so convert from string
const pubSignals = publicInputs.map(s => ethers.BigNumber.from(s));
try {
const isValid = await verifier.verifyProof(pA, pB, pC, pubSignals);
return isValid;
} catch (error) {
console.error("On-chain verification failed:", error);
return false;
}
}
// Example usage:
// const provider = new ethers.JsonRpcProvider("YOUR_RPC_URL");
// const proof = { /* ... from Rust prover ... */ };
// const publicInputs = ["12345"];
// const isValid = await verifyOnChain(provider, proof, publicInputs);
// console.log(`On-chain verification result: ${isValid}`);
So sánh Kiến trúc & Đánh đổi
| Tính năng/Khía cạnh | Circom + SnarkJS (JS Prover) | Circom + Rust (arkworks Prover) |
|---|---|---|
| Ngôn ngữ Prover | JavaScript/TypeScript | Rust |
| Hiệu suất (Chứng minh) | Chậm hơn, dựa trên WASM | Nhanh hơn, Rust native |
| Tạo Witness | SnarkJS (WASM) | SnarkJS (WASM) hoặc Rust tùy chỉnh |
| Tải Proving Key | Trực tiếp từ .zkey | Yêu cầu phân tích cú pháp tùy chỉnh hoặc xử lý cụ thể ark-circom |
| Xác minh (Off-chain) | SnarkJS (JS) | ark-groth16 (Rust) |
| Xác minh (On-chain) | Solidity do SnarkJS tạo | Solidity do SnarkJS tạo |
| Độ trưởng thành của Hệ sinh thái | Cao đối với Circom/SnarkJS | Cao đối với arkworks (mật mã chung), đang phát triển đối với ZKP |
| Trải nghiệm Nhà phát triển | Dễ dàng hơn cho các nhà phát triển web | Đường cong học tập dốc hơn đối với Rust/mật mã |
| Trường hợp Sử dụng | Chứng minh phía client, bằng chứng nhỏ hơn | Chứng minh phía server, thông lượng cao, bằng chứng lớn |
Những Vấn đề & Khắc phục sự cố trong Sản xuất
-
Không khớp phần tử trường: Tất cả các phép tính trong ZKP đều nằm trên một trường hữu hạn (ví dụ:
Frcho BN254). Đảm bảo tất cả các đầu vào (riêng tư và công khai) nằm trong modulo của trường này. Các số lớn phải được xử lý cẩn thận, thường yêu cầu phân tách bit trong các mạch.- Triệu chứng: Lỗi
Constraint doesn't matchtrong quá trình tạo witness hoặcInvalid prooftrong quá trình xác minh. - Khắc phục: Kiểm tra kỹ các giá trị đầu vào. Nếu sử dụng
BigInttrong JavaScript, đảm bảo chúng được chuyển đổi chính xác thành các phần tử trường. Trong Circom,Num2Bitsrất quan trọng để kiểm tra phạm vi.
- Triệu chứng: Lỗi
-
Không khớp thiết lập tin cậy: Sử dụng khóa chứng minh (
.zkey) được tạo bằng một R1CS và cố gắng chứng minh chống lại một R1CS khác sẽ thất bại. Tương tự, khóa xác minh (.jsonhoặc hợp đồng Solidity) phải tương ứng với khóa chứng minh chính xác đã sử dụng.- Triệu chứng:
Error: Invalid proofhoặcError: Could not verify prooftrong quá trình xác minh. - Khắc phục: Luôn đảm bảo
.zkey,.r1csvàverification_key.json(hoặc trình xác minh Solidity) có nguồn gốc từ cùng một thiết lập tin cậy và biên dịch mạch. Kiểm soát phiên bản các tệp.zkeyvàverification_key.jsoncủa bạn.
- Triệu chứng:
-
Thứ tự đầu vào cho các tín hiệu công khai: Thứ tự của các đầu vào công khai được truyền cho trình xác minh (cả SnarkJS và Solidity) phải khớp chính xác với thứ tự mà chúng được khai báo là tín hiệu công khai trong mạch Circom. Tín hiệu công khai đầu tiên trong Circom trở thành phần tử đầu tiên trong mảng
_pubSignalstrong Solidity.- Triệu chứng: Bằng chứng được xác minh thành công nhưng đối với các đầu vào công khai không chính xác, hoặc thất bại bất ngờ.
- Khắc phục: Kiểm tra cẩn thận tệp
circuit.jsonhoặc.symđược tạo bởi Circom để xác định thứ tự chính xác của các đầu vào công khai.
-
Tải Proving Key
arkworks: Việc tải trực tiếpsnarkjs.zkeyvào cấu trúcark-groth16'sProvingKeykhông hề đơn giản.ark-circomcung cấp một số tiện ích, nhưng thường yêu cầu phân tích cú pháp tùy chỉnh hoặc sử dụngsnarkjsđể xuấtzkeydưới dạng JSON và sau đó ánh xạ nó.- Triệu chứng: Lỗi giải tuần tự hóa
ark-groth16ProvingKeyhoặc không khớp kiểu phức tạp. - Khắc phục: Đối với sản xuất, hãy cân nhắc sử dụng
ark-circom'sCircomProverđược thiết kế để hoạt động với các tạo phẩm Circom, hoặc triển khai một trình phân tích cú pháp JSON mạnh mẽ cho xuấtzkey. Ngoài ra, hãy tạoProvingKeytrực tiếp trongarkworksnếu bạn kiểm soát toàn bộ quá trình thiết lập.
- Triệu chứng: Lỗi giải tuần tự hóa
-
Chi phí Gas cho Xác minh On-Chain: Xác minh Groth16 trên Ethereum rất tốn kém. Chi phí gas tăng theo số lượng đầu vào công khai.
- Triệu chứng: Giao dịch hết gas hoặc quá đắt.
- Khắc phục: Giảm thiểu các đầu vào công khai. Gộp các bằng chứng nếu có thể (mặc dù điều này làm tăng độ phức tạp). Cân nhắc các giải pháp layer 2 hoặc xác minh off-chain cho hầu hết các trường hợp sử dụng, chỉ sử dụng xác minh on-chain cho các chuyển đổi trạng thái quan trọng.
Các Câu hỏi Thường gặp
-
Tại sao sử dụng Circom để định nghĩa mạch thay vì trực tiếp trong Rust? Circom cung cấp một ngôn ngữ cấp cao, chuyên biệt được tối ưu hóa để biểu diễn các mạch số học. Nó đơn giản hóa quá trình chuyển đổi các tuyên bố toán học thành các ràng buộc R1CS, một quá trình dễ gây lỗi và dài dòng trong các ngôn ngữ đa năng như Rust. Mặc dù
arkworkscó các nguyên thủy xây dựng mạch, nhưng trừu tượng hóa của Circom thường được ưu tiên cho thiết kế mạch ban đầu và lặp lại nhanh chóng. -
Vai trò của tệp
.zkeylà gì? Tệp.zkeychứa khóa chứng minh (PK) và khóa xác minh (VK) cho một mạch cụ thể sau giai đoạn thiết lập tin cậy. PK được người chứng minh sử dụng để tạo bằng chứng, và VK được người xác minh sử dụng để kiểm tra bằng chứng. Đây là một tạo phẩm quan trọng phải được bảo mật và kiểm soát phiên bản. -
Tôi có thể sử dụng một hệ thống chứng minh khác như Plonk hoặc Marlin không? Có,
arkworkshỗ trợ các hệ thống chứng minh khác. Tuy nhiên, các công cụ (Circom, SnarkJS) chủ yếu được tối ưu hóa cho Groth16. Sử dụng các hệ thống khác sẽ yêu cầu các cratearkworkskhác (ví dụ:ark-plonk) và có thể tích hợp tùy chỉnh để tạo witness và xác minh, vìsnarkjskhông trực tiếp hỗ trợ chúng cho tất cả các hoạt động. -
Làm cách nào để xử lý các đầu vào riêng tư lớn (ví dụ: một hash 256-bit) trong Circom? Circom hoạt động trên một trường hữu hạn, thường là
Frcủa BN254 (254 bit). Đối với các đầu vào lớn hơn thế này, hoặc đối với các hoạt động yêu cầu thao tác cấp bit, bạn phải phân tách số lớn thành các bit hoặc limb cấu thành của nó bằng cách sử dụng các thành phần nhưNum2Bitshoặc các mạch số học đa limb tùy chỉnh. Điều này làm tăng kích thước mạch và thời gian chứng minh. -
Thiết lập tin cậy có thực sự "đáng tin cậy" không? Những rủi ro là gì? Thiết lập tin cậy Groth16 là một thành phần quan trọng. Nếu bất kỳ người tham gia nào trong nghi thức thiết lập giữ lại phần bí mật của họ, họ có thể giả mạo bằng chứng. Vì lý do này, các nghi thức tính toán đa bên (MPC) được sử dụng, nơi nhiều người tham gia đóng góp, và giả định rằng ít nhất một người tham gia là trung thực và loại bỏ bí mật của họ. Đối với các ứng dụng bảo mật cao, việc sử dụng một nghi thức MPC được thiết lập tốt, được kiểm toán công khai là tối quan trọng.
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Tìm hiểu Lifetimes trong Rust
Nắm vững lifetimes và borrow checker của Rust: hiểu variance của tham chiếu, elision lifetime ẩn danh so với có tên, và tránh các xung đột trình biên dịch phức tạp.
Read more
Xây dựng Microservice hiệu suất cao với Rust và Axum: Hướng dẫn sản xuất hoàn chỉnh
Hướng dẫn toàn diện về xây dựng microservice hiệu suất cao bằng Rust và Axum: hướng dẫn sản xuất hoàn chỉnh với kiến trúc cấp độ sản xuất và các ví dụ mã.
Read more
Tương lai của WebAssembly trong điện toán biên: Kiến trúc, WASI 0.2 và các điểm chuẩn
Khám phá cách WebAssembly (Wasm) và WASI 0.2 đang định nghĩa lại điện toán biên với thời gian khởi động lạnh micro giây, bảo mật dựa trên khả năng và các thành phần Rust.
Read more