•27 min read

RustとCircomによるゼロ知識証明:SnarkJS検証と本番環境ガイド

RustとCircomによるゼロ知識証明:SnarkJS検証と本番環境ガイド

ゼロ知識証明(Zero-Knowledge Proofs、ZKP)は、ある当事者(証明者)が別の当事者(検証者)に対し、あるステートメントが真実であることを、そのステートメントの有効性以外のいかなる情報も開示することなく納得させることを可能にします。このガイドでは、Circomを用いた回路定義、arkworksを用いたRustでの証明生成、SnarkJSを用いた検証(Solidityとの統合を含む)によるZKPの実践的な実装について詳しく説明します。

Audio Briefing
0:00 / 0:00

ZKPの基礎とアーキテクチャ

ZKPシステムの中核には、以下の要素があります。

  1. ステートメント定義: 証明されるべき数学的なステートメント。
  2. 回路設計: ステートメントを算術回路に変換すること。
  3. ウィットネス生成: 回路を満たす秘密入力(ウィットネス)を計算すること。
  4. 証明生成: 回路とウィットネスに基づいて暗号学的証明を作成すること。
  5. 証明検証: 公開入力に対して証明の有効性を確認すること。

私たちは、その効率性と幅広い採用実績から、zk-SNARKs(Zero-Knowledge Succinct Non-Interactive Argument of Knowledge)、特にGroth16に焦点を当てます。

アーキテクチャの概要

私たちの本番環境のアーキテクチャは以下の要素を含みます。

  • Circom: 算術回路を定義するためのドメイン固有言語(DSL)。R1CS(Rank-1 Constraint System)とウィットネス生成用のWASMにコンパイルされます。
  • Rust (arkworks): 暗号プリミティブ用の高性能ライブラリで、Groth16の証明生成に使用されます。これは専用のマイクロサービスで実行できます。
  • SnarkJS: ウィットネス生成(CircomからのWASM出力を使用)および証明検証(オフチェーンとSolidity検証者コントラクトを介したオンチェーンの両方)のためのJavaScriptライブラリ。
  • Solidity: オンチェーンでの証明検証のためのスマートコントラクト言語。
Advertisement

Circomによる回路設計

Circomは、算術回路を定義するためのDSLです。有限体上の二次方程式のセットであるR1CS表現にコンパイルされます。

例: 同一性証明回路 (identity.circom)

この回路は、公開入力identity_hashと等しいという制約を満たす秘密入力xの知識を証明します。これは簡略化された同一性証明です。

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();

例: 範囲証明回路 (range.circom)

この回路は、秘密入力xがxを明かすことなく特定の範囲[min, max]内にあることを証明します。ビット分解アプローチを使用します。

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);

Circom回路のコンパイル

これらの回路をコンパイルするには、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

これにより、以下が生成されます。

  • .r1cs: R1CS制約システム。
  • _js/: ウィットネス生成用のwitness_calculator.jsと*.wasmを含むディレクトリ。
  • .sym: デバッグシンボル。
  • .json: 回路情報。

トラステッドセットアップ

Groth16にはトラステッドセットアップが必要です。本番環境では、マルチパーティ計算(MPC)セレモニーを使用してください。開発用には、snarkjsがフェーズ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

identity_final.zkeyとrange_final.zkeyファイルには証明鍵が、verification_key_identity.jsonとverification_key_range.jsonには検証鍵が含まれています。

SnarkJSによるウィットネス生成

ウィットネス生成は通常、クライアントサイドまたは専用サービスで行われます。snarkjsはこれのためのJavaScript APIを提供します。

// 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)
Advertisement

Rust (arkworks)による証明生成

Rustとarkworksは、ZKP操作のための堅牢で高性能な環境を提供します。R1CSのロードにはark-circomを、証明生成にはark-groth16を使用します。

まず、Rustプロジェクトをセットアップします。

cargo new --bin zkp_prover
cd zkp_prover

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"

次に、証明生成のためのRustコードです。この例では、identity_final.zkeyとidentity.r1csファイルがアクセス可能であることを前提としています。

// 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(&params_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(())
}

Rustプロバーに関する重要な注意点: Rustの例におけるgenerate_random_parametersの呼び出しは、デモンストレーションとローカルテスト用です。本番環境では、証明鍵(PK)はトラステッドセットアップ中に生成されたzkey_pathからロードされ、再生成されることはありません。snarkjs .zkeyファイルからProvingKeyをark-groth16に直接ロードすることは、フォーマットの違いにより簡単ではありません。一般的な回避策としては、snarkjs zkey export jsonを使用し、そのJSONをark-groth16のProvingKey構造にパースするか、ark-circomのCircomProver(zkeyのロードを処理します)を使用する方法があります。提供されているGroth16Proof構造体は、ark-groth16証明をSnarkJS/Solidityと互換性のあるフォーマットにシリアライズするためのものです。

SnarkJSによる証明検証

SnarkJSは、verification_key.jsonと生成された証明を使用して、オフチェーンで証明を検証できます。

// 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)

RustのGroth16Proof構造体は、snarkjs.groth16.verifyが期待するフォーマットにシリアライズされるように設計されています。SnarkJSのpi_a、pi_b、pi_cフィールドは、ark-groth16のa、b、cに対応します。

Solidityによるオンチェーン検証

オンチェーン検証の場合、snarkjsは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

生成されたverifier_identity.solには、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.
    }
}

これをDAppから呼び出すには、フロントエンドまたはバックエンドサービスから証明コンポーネント(pi_a、pi_b、pi_c)と公開シグナルを渡します。

// 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}`);

アーキテクチャとトレードオフの比較

機能/側面Circom + SnarkJS (JS Prover)Circom + Rust (arkworks Prover)
Prover言語JavaScript/TypeScriptRust
パフォーマンス (証明)遅い、WASMベース速い、ネイティブRust
ウィットネス生成SnarkJS (WASM)SnarkJS (WASM) またはカスタムRust
証明鍵のロード.zkeyから直接カスタムパースまたはark-circom固有の処理が必要
検証 (オフチェーン)SnarkJS (JS)ark-groth16 (Rust)
検証 (オンチェーン)SnarkJS生成SoliditySnarkJS生成Solidity
エコシステムの成熟度Circom/SnarkJSは高いarkworksは高い(一般的な暗号)、ZKPは成長中
開発者体験Web開発者には簡単Rust/暗号には学習曲線が急
ユースケースクライアントサイドでの証明、小規模な証明サーバーサイドでの証明、高スループット、大規模な証明

本番環境での注意点とトラブルシューティング

  1. フィールド要素の不一致: ZKPにおけるすべての計算は有限体(例: BN254のFr)上で行われます。すべての入力(秘密入力と公開入力)がこの体のモジュラス内にあることを確認してください。大きな数値は慎重に処理する必要があり、多くの場合、回路内でビット分解が必要です。

    • 症状: ウィットネス生成中にConstraint doesn't matchエラーが発生したり、検証中にInvalid proofが発生したりする。
    • 修正: 入力値を再確認してください。JavaScriptでBigIntを使用している場合は、それらがフィールド要素に正しく変換されていることを確認してください。Circomでは、Num2Bitsが範囲チェックに不可欠です。
  2. トラステッドセットアップの不一致: あるR1CSで生成された証明鍵(.zkey)を使用し、別のR1CSに対して証明しようとすると失敗します。同様に、検証鍵(.jsonまたはSolidityコントラクト)は、使用された正確な証明鍵に対応している必要があります。

    • 症状: 検証中にError: Invalid proofまたはError: Could not verify proofが発生する。
    • 修正: .zkey、.r1cs、およびverification_key.json(またはSolidity検証者)が同じトラステッドセットアップと回路コンパイルから生成されたものであることを常に確認してください。.zkeyおよびverification_key.jsonファイルをバージョン管理してください。
  3. 公開シグナルの入力順序: 検証者(SnarkJSとSolidityの両方)に渡される公開入力の順序は、Circom回路で公開シグナルとして宣言された順序と完全に一致する必要があります。Circomの最初の公開シグナルは、Solidityの_pubSignals配列の最初の要素になります。

    • 症状: 証明は正常に検証されるが、誤った公開入力に対して検証されるか、予期せず失敗する。
    • 修正: Circomによって生成されたcircuit.jsonまたは.symファイルを注意深く検査し、公開入力の正確な順序を特定してください。
  4. arkworks証明鍵のロード: snarkjs .zkeyをark-groth16のProvingKey構造体に直接ロードすることは簡単ではありません。ark-circomはいくつかのユーティリティを提供しますが、多くの場合、カスタムパースまたはsnarkjsを使用してzkeyをJSONとしてエクスポートし、それをマッピングする必要があります。

    • 症状: ark-groth16 ProvingKeyのデシリアライズエラーまたは複雑な型不一致。
    • 修正: 本番環境では、Circomアーティファクトと連携するように設計されたark-circomのCircomProverを使用するか、zkeyエクスポート用の堅牢なJSONパーサーを実装することを検討してください。あるいは、セットアッププロセス全体を制御できる場合は、ProvingKeyをarkworks内で直接生成します。
  5. オンチェーン検証のガス代: EthereumでのGroth16検証は高価です。ガス代は公開入力の数に比例して増加します。

    • 症状: トランザクションがガス切れになるか、法外に高価になる。
    • 修正: 公開入力を最小限に抑えます。可能であれば証明をバッチ処理します(ただし、これにより複雑さが増します)。ほとんどのユースケースではレイヤー2ソリューションまたはオフチェーン検証を検討し、重要な状態遷移にのみオンチェーン検証を使用します。

よくある質問

  1. 回路定義にRustで直接ではなくCircomを使用する理由は何ですか? Circomは、算術回路を表現するために最適化された、高レベルのドメイン固有言語を提供します。これにより、数学的ステートメントをR1CS制約に変換するプロセスが簡素化されます。これは、Rustのような汎用言語ではエラーが発生しやすく、冗長です。arkworksには回路構築プリミティブがありますが、初期の回路設計と迅速なイテレーションにはCircomの抽象化が好まれることがよくあります。

  2. .zkeyファイルの役割は何ですか? .zkeyファイルには、トラステッドセットアップフェーズ後の特定の回路の証明鍵(PK)と検証鍵(VK)が含まれています。PKは証明者が証明を生成するために使用され、VKは検証者が証明をチェックするために使用されます。これは、保護され、バージョン管理されるべき重要なアーティファクトです。

  3. PlonkやMarlinのような別の証明システムを使用できますか? はい、arkworksは他の証明システムをサポートしています。ただし、ツール(Circom、SnarkJS)は主にGroth16に最適化されています。他のシステムを使用するには、異なるarkworksクレート(例: ark-plonk)が必要となり、snarkjsがすべての操作を直接サポートしているわけではないため、ウィットネス生成と検証のためのカスタム統合が必要になる可能性があります。

  4. Circomで大きな秘密入力(例: 256ビットハッシュ)をどのように処理しますか? Circomは有限体、通常はBN254のFr(254ビット)上で動作します。これより大きな入力、またはビットレベルの操作を必要とする操作の場合、Num2Bitsのようなコンポーネントやカスタムのマルチリム算術回路を使用して、大きな数値を構成ビットまたはリムに分解する必要があります。これにより、回路サイズと証明時間が長くなります。

  5. トラステッドセットアップは本当に「信頼できる」のですか?リスクは何ですか? Groth16のトラステッドセットアップは重要なコンポーネントです。セットアップセレモニーの参加者のいずれかが秘密の共有を保持している場合、彼らは偽の証明を偽造する可能性があります。このため、多くの参加者が貢献し、少なくとも1人の参加者が正直で秘密を破棄すると仮定されるマルチパーティ計算(MPC)セレモニーが使用されます。高セキュリティアプリケーションの場合、確立された公開監査済みのMPCセレモニーを使用することが最も重要です。

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement
Rustのライフタイムを理解する
rust

Rustのライフタイムを理解する

Rustのライフタイムとボローチェッカーを習得し、参照のvariance、匿名と名前付きのライフタイム省略を理解し、複雑なコンパイラの競合を回避しましょう。

Read more