JS `Zero-Knowledge Proofs` (`ZK-SNARKs`) 库 (`snarkjs`) 在浏览器中的集成与应用

各位观众老爷们,晚上好!我是今天的主讲人,江湖人称“代码搬运工”,今天咱们聊点刺激的——如何在浏览器里玩转零知识证明,特别是用 snarkjs 这个神器。

开场白:啥是零知识证明?

想象一下,你想证明你是个老司机,但又不想暴露你的驾照信息,甚至不想让别人知道你到底有没有驾照,这就是零知识证明的精髓:证明“我知道”,但不泄露“我知道什么”。

更学术点说,零知识证明 (Zero-Knowledge Proof, ZKP) 是一种密码学协议,允许一方(证明者)向另一方(验证者)证明某个陈述是真实的,而无需透露除陈述本身之外的任何信息。

为什么要在浏览器里搞 ZKP?

你可能会问,这玩意儿听起来高大上,跟咱们前端程序员有啥关系?关系大了!

  • 隐私保护: 在用户数据敏感的场景下,比如身份验证、投票、支付等,ZKP 可以保护用户隐私,避免数据泄露。
  • 计算外包: 你可以将复杂的计算放到浏览器里进行,然后用 ZKP 证明计算结果的正确性,而无需信任服务器。
  • 链上验证: 在区块链应用中,ZKP 可以让链上验证更高效,降低交易成本。

snarkjs:前端 ZKP 的瑞士军刀

snarkjs 是一个 JavaScript 库,专门用于生成和验证 ZK-SNARKs (Succinct Non-interactive ARguments of Knowledge)。它提供了以下核心功能:

  • 电路描述: 使用 Circom 语言描述你的计算逻辑。
  • 电路编译: 将 Circom 电路编译成 R1CS (Rank-1 Constraint System) 形式。
  • 可信设置: 生成 proving key 和 verifying key,这是 ZK-SNARKs 的关键。
  • 证明生成: 使用 proving key 和输入数据生成证明。
  • 证明验证: 使用 verifying key 和公共输入数据验证证明。

浏览器集成:手把手教你撸代码

废话不多说,咱们直接上代码,看看如何在浏览器里使用 snarkjs

1. 环境搭建

首先,你需要安装 snarkjs 和相关依赖。这里我们使用 npm 或者 yarn:

npm install snarkjs circomlib --save
# 或者
yarn add snarkjs circomlib

由于 snarkjs 依赖一些 Node.js 的模块,我们需要使用 webpack 或者 Parcel 等打包工具将代码打包成浏览器可用的 bundle。 这里以 webpack 为例:

npm install webpack webpack-cli --save-dev

创建一个 webpack.config.js 文件:

const path = require('path');

module.exports = {
    entry: './src/index.js', //你的入口文件
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        library: 'zkpLib', // 暴露给全局的变量名,方便在HTML中调用
        libraryTarget: 'umd', // 使用通用模块定义
        globalObject: 'this', // 兼容不同的环境
    },
    mode: 'development', // 设置为开发模式
    devtool: 'inline-source-map', // 方便调试
    resolve: {
        fallback: {
            "fs": false,
            "tls": false,
            "net": false,
            "path": false,
            "zlib": false,
            "http": false,
            "https": false,
            "stream": false,
            "crypto": false, // 避免crypto报错
        },
    },
};

创建一个 src/index.js 作为入口文件,我们将在其中编写 ZKP 相关的代码。

2. 定义 Circom 电路

Circom 是一种领域特定语言 (DSL),用于描述算术电路。咱们先来一个简单的例子:证明你知道一个数的平方根。

创建一个名为 sqrt.circom 的文件:

pragma circom 2.0.0;

template Sqrt() {
    signal input in;
    signal input sqrt;
    signal output out;

    in === sqrt * sqrt;
    out <== in;
}

component main {public [in]} = Sqrt();

这段代码定义了一个名为 Sqrt 的模板,它接受一个输入 in 和一个输入 sqrt,并验证 in 是否等于 sqrt 的平方。out 只是将 in 赋值过去,没什么实际意义,仅仅是为了满足 Circom 语法的要求,电路必须有输出。 component main {public [in]} = Sqrt(); 定义了主电路,并将 in 设置为公共输入。

3. 编译 Circom 电路

使用 snarkjs 提供的命令行工具编译 Circom 电路:

snarkjs circom compile sqrt.circom -o sqrt.json

这条命令会将 sqrt.circom 编译成 sqrt.json,其中包含了电路的 R1CS 描述。

4. 生成 proving key 和 verifying key

这一步是 ZK-SNARKs 的关键,我们需要生成 proving key 和 verifying key。这里我们使用 snarkjs powersOfTau 命令生成 powers of tau 文件,然后使用 snarkjs groth16 setup 命令生成 keys。

首先,生成 powers of tau 文件:

snarkjs powersOfTau new bn128 12 pot12.ptau
snarkjs powersOfTau contribute pot12.ptau pot12_1.ptau --name="First contribution" -v
snarkjs powersOfTau prepare pot12_1.ptau pot12_final.ptau

然后,生成 proving key 和 verifying key:

snarkjs groth16 setup sqrt.r1cs pot12_final.ptau proving_key.json verifying_key.json

5. 在浏览器中使用 snarkjs

现在,我们可以在浏览器中使用 snarkjs 生成和验证证明了。

src/index.js 中编写以下代码:

import * as snarkjs from 'snarkjs';

async function generateProof(input) {
    // 导入电路描述、proving key 和 verifying key
    const circuit = require('./sqrt.json');
    const provingKey = require('./proving_key.json');

    // 计算 witness
    const witness = await circuit.calculateWitness(input);

    // 生成证明
    const { proof, publicSignals } = await snarkjs.groth16.prove(provingKey, witness);

    return { proof, publicSignals };
}

async function verifyProof(proof, publicSignals) {
    // 导入 verifying key
    const verifyingKey = require('./verifying_key.json');

    // 验证证明
    const verificationKey = {
        protocol: 'groth16',
        curve: 'bn128',
        vk_alpha_1: verifyingKey.alpha_1,
        vk_beta_2: verifyingKey.beta_2,
        vk_gamma_2: verifyingKey.gamma_2,
        vk_delta_2: verifyingKey.delta_2,
        vk_alphabeta_11: verifyingKey.alphabeta_11,
        vk_gamma_abc: verifyingKey.gamma_abc,
    };

    const verified = await snarkjs.groth16.verify(verificationKey, publicSignals, proof);

    return verified;
}

// 暴露函数给全局
window.generateProof = generateProof;
window.verifyProof = verifyProof;

6. 创建 HTML 文件

创建一个 index.html 文件,用于调用 JavaScript 函数:

<!DOCTYPE html>
<html>
<head>
    <title>snarkjs Example</title>
</head>
<body>
    <h1>snarkjs Example</h1>
    <button onclick="testZKP()">Run ZKP</button>
    <div id="result"></div>

    <script src="./dist/bundle.js"></script>
    <script>
        async function testZKP() {
            const input = { in: "9" }; // 输入要证明的数的平方
            const sqrt_val = 3;

            // 修改电路以接受sqrt作为输入
            const witness = { in: input.in, sqrt: sqrt_val };

            const { proof, publicSignals } = await window.generateProof(witness);
            const verified = await window.verifyProof(proof, publicSignals);

            const resultDiv = document.getElementById('result');
            resultDiv.innerHTML = `Proof Verification Result: ${verified}`;
        }
    </script>
</body>
</html>

7. 打包和运行

使用 webpack 打包代码:

npx webpack

在浏览器中打开 index.html,点击按钮,你就可以看到证明验证的结果了。

代码解释

  • generateProof(input) 函数:
    • 导入编译后的电路描述、proving key。
    • 根据输入计算 witness。这里需要注意的是,witness 包含了所有的输入信号,包括公共输入和私有输入。
    • 使用 snarkjs.groth16.prove() 函数生成证明。
    • 返回 proof 和 publicSignals。
  • verifyProof(proof, publicSignals) 函数:
    • 导入 verifying key。
    • 使用 snarkjs.groth16.verify() 函数验证证明。
    • 返回验证结果。

注意事项

  • 可信设置: ZK-SNARKs 的安全性依赖于可信设置的安全性。在生产环境中,需要使用 MPC (Multi-Party Computation) 等技术来生成 proving key 和 verifying key,以避免单点故障。
  • 电路设计: 电路设计是 ZK-SNARKs 的关键。一个好的电路设计可以提高证明效率,降低计算成本。
  • 性能优化: ZK-SNARKs 的计算复杂度较高,需要进行性能优化。可以使用 WebAssembly 等技术来提高计算速度。
  • 数据类型: 确保输入数据的类型与电路定义中的类型一致。snarkjs 通常使用字符串来表示大整数。
  • 文件路径: 确保文件路径正确,特别是电路描述、proving key 和 verifying key 的路径。
  • 浏览器兼容性: snarkjs 在不同的浏览器中可能存在兼容性问题,需要进行测试。
  • 内存限制: 浏览器环境的内存有限,需要注意控制电路的复杂度,避免内存溢出。

表格总结

| 步骤 | 命令/代码 | 描述
| 依赖 | snarkjs, circomlib, webpack | snarkjs 是 ZKP 核心库,circomlib 提供常用电路组件,webpack 用于打包。 咱们今天就先聊到这儿,希望大家有所收获! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注