Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第五十二章 零知识证明

证明我知道秘密,但不透露秘密本身

零知识证明(Zero-Knowledge Proof, ZKP)是密码学中最迷人的概念之一。它允许证明者向验证者证明某个陈述为真,而无需透露任何超出陈述真实性之外的信息。本章将介绍零知识证明的基本原理、主要类型及其在 Rust 中的实现。

52.1 什么是零知识证明

直观理解

想象一个场景:Alice 声称她知道一个迷宫的秘密出口,但她不想透露出口的位置。她可以这样做:

  1. Alice 进入迷宫
  2. Bob 在入口等待,随机喊“从 A 口出“或“从 B 口出“
  3. Alice 从指定的出口出现
  4. 重复多次

如果 Alice 真的知道秘密路径,她每次都能从指定出口出现;如果她只是运气好,连续多次猜对的概率会指数级下降。关键是,Bob 从未看到 Alice 在迷宫中的路径——他只知道 Alice“似乎“知道出口。

零知识证明的三要素

一个完整的零知识证明系统必须满足三个性质:

  1. 完备性(Completeness):如果陈述为真,诚实的证明者可以说服诚实的验证者
  2. 可靠性(Soundness):如果陈述为假,任何欺骗性的证明者都无法以不可忽略的概率说服验证者
  3. 零知识性(Zero-Knowledge):验证者除了“陈述为真“之外,无法获得任何额外信息

形式化定义

公共输入 x
证明者私有输入 w(见证/witness)

关系 R(x, w) = true 表示"w 是 x 的有效见证"

证明者知道 w 使得 R(x, w) = true
    ↓
零知识证明协议
    ↓
验证者确信"存在 w 使得 R(x, w) = true"
但不知道 w 的任何信息

52.2 交互式 vs 非交互式

交互式零知识证明

经典的零知识证明是交互式的:证明者和验证者需要进行多轮通信。

示例:离散对数的零知识证明

公共参数:循环群 G,生成元 g,素数阶 p
公共输入:y = g^x mod p(Alice 声称知道 x)

1. Alice 选择随机数 r,计算 a = g^r mod p,发送 a 给 Bob
2. Bob 选择随机挑战 c,发送给 Alice
3. Alice 计算 s = r + c * x mod (p-1),发送 s 给 Bob
4. Bob 验证:g^s ≡ a * y^c (mod p)

验证等式成立的原因:

g^s = g^(r + c*x) = g^r * g^(c*x) = g^r * (g^x)^c = a * y^c

这个协议是零知识的,因为 Bob 可以从自己生成的随机数构造出与真实协议不可区分的“模拟“ transcript。

非交互式零知识证明(NIZK)

交互式证明在实际应用中很不方便。Fiat-Shamir 启发式方法可以将交互式协议转换为非交互式

将验证者的随机挑战替换为哈希函数的输出:
    c = Hash(g, y, a)

这样证明者可以一次性生成完整证明,验证者独立验证。

非交互式零知识证明的优势:

  • 证明可以公开广播,任何人都可以验证
  • 适合区块链等无需许可的环境
  • 证明可以被重复使用

交互式 vs 非交互式对比

特性交互式 ZKP非交互式 ZKP
通信轮数多轮一轮(证明者 → 验证者)
可公开验证
应用场景私有协议区块链、公开审计
转换方法Fiat-Shamir 启发式

52.3 zk-SNARKs 简介

什么是 zk-SNARK

zk-SNARK(Zero-Knowledge Succinct Non-Interactive Argument of Knowledge)是目前最实用的零知识证明系统之一,具备以下特性:

  • 零知识(Zero-Knowledge):不泄露见证信息
  • 简洁(Succinct):证明大小恒定(几百字节),验证时间极短(毫秒级)
  • 非交互(Non-Interactive):单条消息完成证明
  • 知识论证(Argument of Knowledge):证明者确实“知道“见证,而不仅仅是陈述为真

zk-SNARK 的工作原理(概述)

zk-SNARK 的核心思想是将计算转换为算术电路,然后将电路转换为多项式约束:

程序/计算
    ↓
算术电路(加法门、乘法门)
    ↓
R1CS(Rank-1 Constraint System,一阶约束系统)
    ↓
QAP(Quadratic Arithmetic Program,二次算术程序)
    ↓
多项式承诺(如 KZG、FRI)
    ↓
简洁证明

关键概念

  • 可信设置(Trusted Setup):某些 zk-SNARK 变体需要生成公共参考字符串(CRS),这个过程必须安全执行
  • 通用可信设置:如 Groth16 需要为每个电路单独设置
  • 透明设置:如 STARKs、Bulletproofs 不需要可信设置

主流 zk-SNARK 方案

方案证明大小验证时间可信设置后量子安全
Groth16192 字节1.5ms每个电路一次
PLONK~400 字节~3ms通用(一次)
STARKs~50KB~10ms无需
Bulletproofs~1KB~线性无需

零知识证明的应用场景

1. 隐私交易

区块链上的交易通常是透明的。ZKP 可以隐藏交易金额和参与方,同时保证交易有效性:

传统交易:
  Alice --10 BTC--> Bob(全网可见)

Zcash 隐私交易:
  ??? --??--> ???(金额和地址隐藏)
  但 ZKP 证明:发送方有足够余额,交易有效

2. 身份验证

证明你年满 18 岁,而不透露出生日期;证明你的信用评分高于阈值,而不透露具体分数。

3. 计算外包验证

将复杂计算外包给第三方,用 ZKP 验证计算结果的正确性,而无需重新执行计算。

4. 区块链扩容(Rollups)

将大量交易在链下执行,只向主链提交一个简洁的 ZKP 证明,证明所有链下交易都有效。

52.4 Rust 实现:零知识证明示例

使用 bellman 库实现简单电路

bellman 是 Zcash 团队开发的 Rust zk-SNARK 库,基于 Groth16 证明系统。

# Cargo.toml
[dependencies]
bellman = "0.14"
ff = "0.13"
bls12_381 = "0.8"
rand = "0.8"
#![allow(unused)]
fn main() {
use bellman::{Circuit, ConstraintSystem, SynthesisError};
use bls12_381::Scalar;
use ff::PrimeField;

// 证明:我知道 x 和 y,使得 x * y = public_output
struct MultiplicationCircuit {
    x: Option<Scalar>,
    y: Option<Scalar>,
}

impl Circuit<Scalar> for MultiplicationCircuit {
    fn synthesize<CS: ConstraintSystem<Scalar>>(
        self,
        cs: &mut CS,
    ) -> Result<(), SynthesisError> {
        // 分配私有输入 x
        let x = cs.alloc(|| "x", || {
            self.x.ok_or(SynthesisError::AssignmentMissing)
        })?;

        // 分配私有输入 y
        let y = cs.alloc(|| "y", || {
            self.y.ok_or(SynthesisError::AssignmentMissing)
        })?;

        // 分配公共输出
        let public_output = self.x.and_then(|x| {
            self.y.map(|y| x * y)
        });

        let output = cs.alloc_input(|| "output", || {
            public_output.ok_or(SynthesisError::AssignmentMissing)
        })?;

        // 约束:x * y = output
        cs.enforce(
            || "multiplication constraint",
            |lc| lc + x,
            |lc| lc + y,
            |lc| lc + output,
        );

        Ok(())
    }
}
}

生成和验证证明

use bellman::groth16::{generate_random_parameters, create_random_proof, verify_proof, PreparedVerifyingKey};
use bls12_381::{Bls12, Scalar};
use rand::rngs::OsRng;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let rng = &mut OsRng;

    // 1. 可信设置:为电路生成参数
    let params = {
        let c = MultiplicationCircuit { x: None, y: None };
        generate_random_parameters::<Bls12, _, _>(c, rng)?
    };

    // 准备验证密钥(加速验证)
    let pvk = PreparedVerifyingKey::from(params.vk.clone());

    // 2. 创建证明:我知道 3 和 5,它们的乘积是 15
    let public_input = Scalar::from(15);

    let proof = {
        let c = MultiplicationCircuit {
            x: Some(Scalar::from(3)),
            y: Some(Scalar::from(5)),
        };
        create_random_proof(c, &params, rng)?
    };

    // 3. 验证证明
    let is_valid = verify_proof(&pvk, &proof, &[public_input])?;

    println!("证明有效: {}", is_valid);

    // 尝试验证错误的输入
    let wrong_input = Scalar::from(100);
    let is_invalid = verify_proof(&pvk, &proof, &[wrong_input]).is_ok();
    println!("错误输入验证通过: {}", is_invalid);

    Ok(())
}

简化的零知识证明演示(不依赖外部库)

为了理解零知识证明的核心原理,我们实现一个极简的“颜色证明“示例——基于图同态的零知识证明概念:

use sha2::{Sha256, Digest};
use rand::Rng;

// 简化的承诺方案
fn commit(secret: &str, nonce: &[u8]) -> String {
    let mut hasher = Sha256::new();
    hasher.update(secret.as_bytes());
    hasher.update(nonce);
    format!("{:x}", hasher.finalize())
}

// 证明者声称"我知道一个满足某种性质的值"
// 这里简化为:我知道一个值,其哈希的前 4 位是 0
struct Prover {
    secret: String,
}

impl Prover {
    fn new(secret: String) -> Self {
        Prover { secret }
    }

    // 生成承诺
    fn commit_secret(&self) -> (String, Vec<u8>) {
        let nonce: Vec<u8> = (0..16).map(|_| rand::thread_rng().gen::<u8>()).collect();
        let commitment = commit(&self.secret, &nonce);
        (commitment, nonce)
    }

    // 响应挑战
    fn respond(&self, challenge: u8) -> String {
        if challenge == 0 {
            // 揭示秘密
            self.secret.clone()
        } else {
            // 揭示其他信息(简化示例)
            format!("response_{}", challenge)
        }
    }
}

struct Verifier {
    commitment: String,
}

impl Verifier {
    fn new(commitment: String) -> Self {
        Verifier { commitment }
    }

    // 生成随机挑战
    fn challenge(&self) -> u8 {
        rand::thread_rng().gen_range(0..2)
    }

    // 验证响应
    fn verify(&self, response: &str, nonce: &[u8], challenge: u8) -> bool {
        if challenge == 0 {
            // 验证承诺是否对应揭示的秘密
            let recomputed = commit(response, nonce);
            recomputed == self.commitment
        } else {
            // 其他验证逻辑
            response.starts_with("response_")
        }
    }
}

fn main() {
    println!("=== 简化的零知识证明演示 ===\n");

    // 场景:证明者知道一个"特殊"字符串(比如 SHA-256 前导 0)
    let secret = "my_secret_value_42".to_string();
    let prover = Prover::new(secret);

    // 证明者生成承诺
    let (commitment, nonce) = prover.commit_secret();
    println!("承诺: {}", &commitment[..16]);

    // 验证者存储承诺并发起挑战
    let verifier = Verifier::new(commitment);

    // 多轮交互
    let mut success_count = 0;
    let rounds = 10;

    for round in 1..=rounds {
        let challenge = verifier.challenge();
        let response = prover.respond(challenge);
        let valid = verifier.verify(&response, &nonce, challenge);

        println!(
            "轮次 {}: 挑战={}, 验证={}",
            round, challenge, valid
        );

        if valid {
            success_count += 1;
        }
    }

    println!("\n成功率: {}/{} = {:.0}%", success_count, rounds,
             (success_count as f64 / rounds as f64) * 100.0);

    if success_count == rounds {
        println!("验证者确信:证明者知道秘密!");
    }
}

范围证明概念(Bulletproofs 风格)

范围证明是 ZKP 的重要应用:证明一个值在某个范围内,而不透露具体值。

use sha2::{Sha256, Digest};

// 概念演示:证明 value 在 [0, 2^n) 范围内
// 实际实现需要使用 Pedersen 承诺和内部乘积论证

struct RangeProofConcept {
    n: usize,  // 位数
}

impl RangeProofConcept {
    // 将值分解为二进制位
    fn decompose(value: u64, n: usize) -> Vec<bool> {
        let mut bits = Vec::with_capacity(n);
        for i in 0..n {
            bits.push(((value >> i) & 1) == 1);
        }
        bits
    }

    // 验证二进制分解的正确性
    fn verify_decomposition(value: u64, bits: &[bool]) -> bool {
        let reconstructed: u64 = bits.iter()
            .enumerate()
            .map(|(i, &b)| if b { 1u64 << i } else { 0 })
            .sum();
        reconstructed == value
    }

    // 概念:证明每个位是 0 或 1
    fn prove_bit_is_binary(bit: bool) -> &'static str {
        // 在实际 ZKP 中,这转化为约束:b * (1 - b) = 0
        // 即 b 只能是 0 或 1
        if bit {
            "bit = 1: 1 * (1 - 1) = 0 ✓"
        } else {
            "bit = 0: 0 * (1 - 0) = 0 ✓"
        }
    }
}

fn main() {
    let proof = RangeProofConcept { n: 32 };

    let value = 12345u64;
    let bits = RangeProofConcept::decompose(value, 32);

    println!("值: {}", value);
    println!("二进制分解: {:?}", bits);
    println!("分解验证: {}", RangeProofConcept::verify_decomposition(value, &bits));

    println!("\n每位验证:");
    for (i, &bit) in bits.iter().take(8).enumerate() {
        println!("  位 {}: {}", i, RangeProofConcept::prove_bit_is_binary(bit));
    }

    // 范围验证
    let max_value = (1u64 << 32) - 1;
    println!("\n范围验证: 0 <= {} <= {} → {}", value, max_value,
             value <= max_value);
}

使用 arkworks 进行现代 ZKP 开发

arkworks 是 Rust 生态中更现代的零知识证明框架,提供了模块化的代数组件。

#![allow(unused)]
fn main() {
// arkworks 概念示例
use ark_ff::Field;
use ark_bls12_381::Fr;

// 在有限域上运算
fn field_operations() {
    let a = Fr::from(3u64);
    let b = Fr::from(5u64);

    let sum = a + b;
    let product = a * b;
    let inverse = a.inverse().expect("可逆");

    println!("3 + 5 = {:?}", sum);
    println!("3 * 5 = {:?}", product);
    println!("3^-1 = {:?}", inverse);
    println!("3 * 3^-1 = {:?}", a * inverse);  // 应等于 1
}
}

52.5 零知识证明的挑战与前沿

当前挑战

  1. 可信设置的安全性:Groth16 等方案需要可信设置,如果设置过程被泄露,整个系统的安全性将崩溃
  2. 计算开销:生成证明的计算成本仍然很高
  3. 电路复杂性:将程序转换为算术电路需要专业知识
  4. 量子计算威胁:基于椭圆曲线的 ZKP 面临量子计算的潜在威胁(STARKs 是后量子安全的替代方案)

前沿发展方向

  • 硬件加速:GPU、FPGA、ASIC 用于加速证明生成
  • 递归证明:在一个证明中验证另一个证明,实现无限压缩
  • zk-EVM:在零知识证明中执行以太坊虚拟机指令
  • 身份与合规:KYC/AML 与隐私保护的结合

52.6 本章总结

概念说明Rust 生态
交互式 ZKP多轮通信完成证明理论演示
非交互式 ZKP单条消息,可公开验证Fiat-Shamir 转换
zk-SNARK简洁非交互零知识证明bellman, arkworks
zk-STARK透明设置、后量子安全winterfell
范围证明证明值在范围内bulletproofs
电路编译程序 → 算术电路circom-compat

练习建议

  1. 基础练习:运行上述 bellman 乘法电路示例,尝试修改电路证明其他关系(如 x + y = z)。

  2. 中级练习:实现一个简化的离散对数零知识证明(Sigma 协议),使用 Fiat-Shamir 转换为非交互式。

  3. 高级练习:使用 arkworks 构建一个更复杂的电路,如证明你知道一个 Merkle 树中的叶子节点( membership proof)。

  4. 实践项目:研究 Zcash 或 Filecoin 中零知识证明的应用,尝试运行它们的 Rust 节点或客户端。


密码学箴言:零知识证明是密码学的“魔术“——它让你确信对方知道秘密,同时确信自己什么都没学到。在隐私与验证之间,ZKP 找到了完美的平衡。