Groth16 ZKP: 零知识证明

5 阅读3分钟

简介 Groth16 是由 Jens Groth 于 2016 年提出的 zk-SNARK 协议。它是目前区块链领域(如 Zcash, Ethereum L2, Filecoin)应用最广泛的算法。

核心特性:

  • 极致简洁 (Succinct):证明 π\pi 仅包含 3 个群元素 (在 BN254 曲线下仅约 128-200 字节)。
  • 验证极快 (Fast Verification):验证过程仅需 3 次双线性配对 (Pairing) 运算。
  • 需要可信设置 (Trusted Setup):需要为每一个电路单独生成 CRS (Common Reference String)。

整体工作流:

  1. 计算逻辑 (Code) \rightarrow 算术电路 (Arithmetic Circuit)
  2. 算术电路 \rightarrow 一阶约束系统 (R1CS)
  3. R1CS \rightarrow 二次算术程序 (QAP)
  4. Groth16 (基于椭圆曲线和配对的证明生成与验证)

第一节:计算逻辑到算术电路 (Logic to Arithmetic Circuit)

1.1 核心概念转换

零知识证明不直接处理计算机程序(如 C++, Python),而是处理数学多项式。将代码转化为数学形式的第一步是算术化 (Arithmetization)

传统程序 (CPU)算术电路 (ZKP)区别核心
指令流数据流电路没有寄存器,只有导线连接
动态执行 (Loops, Jumps)静态拓扑 (Unrolled Loops)循环必须展开,路径必须固定
布尔运算 (AND, XOR)有限域算术 (+, ×\times)位运算极其昂贵,需用多项式模拟

1.2 算术电路的构成

电路定义在有限域 Fr\mathbb{F}_r 上(模 pp 的整数域)。

  • 导线 (Wires):承载数值(Witness)。
  • 加法门 (Addition Gate)z=x+yz = x + y(计算成本极低,甚至视为免费)。
  • 乘法门 (Multiplication Gate)z=x×yz = x \times y昂贵,决定了生成证明的速度和大小)。

1.3 转换实例:三次方程

假设证明目标:“我知道一个数 xx,使得 x3+x+5=35x^3 + x + 5 = 35

第一步:拍平 (Flattening)

将复杂的嵌套计算拆解为单一操作符(op{+,×}op \in \{+, \times\})的序列。引入中间变量 viv_i

  • 原始逻辑func(x) { return x*x*x + x + 5 == 35 }
  • 拍平后
    1. v1=xxv_1 = x \cdot x
    2. v2=v1xv_2 = v_1 \cdot x
    3. out=v2+x+5out = v_2 + x + 5

第二步:构建电路门 (Circuit Gates)

将上述步骤转化为电路约束。每个乘法关系对应一个门。

  1. 乘法门 1 (M1M_1):

    • 左输入: xx
    • 右输入: xx
    • 输出: v1v_1
    • 代数约束: xxv1=0x \cdot x - v_1 = 0
  2. 乘法门 2 (M2M_2):

    • 左输入: v1v_1
    • 右输入: xx
    • 输出: v2v_2
    • 代数约束: v1xv2=0v_1 \cdot x - v_2 = 0
  3. 加法/线性组合 (作为最终约束):

    • 约束: (v2+x+5)1=35(v_2 + x + 5) \cdot 1 = 35
    • 注:在 R1CS 中,加法通常融合在 AB=CA \cdot B = C 的线性组合中,不需要单独的门。

1.4 处理复杂逻辑 (Difficulties)

对于非代数运算,必须用数学约束模拟:

  1. 布尔约束 (Boolean Check): 证明一个变量 bb 是二进制位(0 或 1)。
    • 公式: b(1b)=0b \cdot (1 - b) = 0
  2. 条件选择 (If-Else / Multiplexer): 逻辑 if (b) y = x else y = z
    • 公式: y=bx+(1b)zy = b \cdot x + (1 - b) \cdot z
    • 注:必须计算所有分支,通过数学公式“选择”结果。
  3. 位运算 (Bitwise Operations): 由于域元素 Fr\mathbb{F}_r 是大整数,无法直接进行位操作。需要针对单 bit (a,b{0,1}a, b \in \{0,1\}) 使用代数公式:
    • XOR (\oplus): ab=a+b2aba \oplus b = a + b - 2ab
      • 验证: 0+0=0,1+0=1,1+12=00+0=0, 1+0=1, 1+1-2=0 (正确)
    • AND (\land): ab=aba \land b = a \cdot b
    • OR (\lor): ab=a+baba \lor b = a + b - ab
    • NOT (¬\neg): ¬a=1a\neg a = 1 - a
  4. 位分解 (Bit Decomposition): 若要对 32 位整数 XX 进行位运算,必须先将其拆解为 32 个变量 b0b31b_0 \dots b_{31},并添加约束:
    • 约束 1 (聚合): X=i=0312ibiX = \sum_{i=0}^{31} 2^i \cdot b_i
    • 约束 2 (二进制检查): 对每个 bib_i 都要约束 bi(1bi)=0b_i(1-b_i)=0
    • 代价:这就是 SHA-256 等哈希函数在 ZK 中极其昂贵的原因(需要数万个约束)。

1.5 总结

  • 输入:一段代码逻辑。
  • 输出:一张包含输入导线、中间导线、输出导线以及乘法门的“图纸”。
  • Witness:使得电路所有门都成立的一组数值向量(例如 [1,35,3,9,27][1, 35, 3, 9, 27])。

第二节:算术电路到 R1CS (Arithmetic Circuit to R1CS)

2.1 什么是 R1CS (Rank-1 Constraint System)

R1CS 是一组定义在有限域上的特殊方程组。它的核心思想是把电路中的每一个乘法门(以及相关的线性组合)都标准化为同一个数学格式

标准公式: 对于每一个逻辑门 ii,必须满足:

(Ais)×(Bis)=(Cis)(\mathbf{A}_i \cdot \mathbf{s}) \times (\mathbf{B}_i \cdot \mathbf{s}) = (\mathbf{C}_i \cdot \mathbf{s})
  • s\mathbf{s} (Solution Vector): 全局变量解向量(Witness)。
  • Ai,Bi,Ci\mathbf{A}_i, \mathbf{B}_i, \mathbf{C}_i: 系数向量。
  • \cdot (Dot Product): 向量点积,代表对变量进行线性组合(加法)。
  • ×\times: 数值乘法。

直译含义(左输入导线的线性组合)×(右输入导线的线性组合)=(输出导线的线性组合)(\text{左输入导线的线性组合}) \times (\text{右输入导线的线性组合}) = (\text{输出导线的线性组合})

2.2 构建解向量 (The Witness Vector s\mathbf{s})

为了用矩阵描述电路,首先要将电路中出现过的所有数值拍平成一个长向量 s\mathbf{s}。 通常结构如下:

s=[ 1, Public Outputs, Public Inputs, Private Inputs, Intermediate Variables ]\mathbf{s} = [\ 1, \ \text{Public Outputs}, \ \text{Public Inputs}, \ \text{Private Inputs}, \ \text{Intermediate Variables} \ ]
  • 常数 1: 必须包含,用于处理加法中的常数项(如 x+5x+5 中的 5,即 5×15 \times 1)。

实例 (x3+x+5=35x^3+x+5=35) 的向量定义: 假设变量顺序为 [one,out,x,v1,v2][one, out, x, v_1, v_2],若 x=3x=3,则:

s=[ 1, 35, 3, 9, 27 ]\mathbf{s} = [\ 1, \ 35, \ 3, \ 9, \ 27 \ ]

2.3 门到约束的转换 (Gate to Constraints)

我们需要为电路中的每一步运算构建 a,b,c\mathbf{a}, \mathbf{b}, \mathbf{c} 三个系数向量。

回顾逻辑:

  1. v1=xxv_1 = x \cdot x
  2. v2=v1xv_2 = v_1 \cdot x
  3. out=(v2+x+5)1out = (v_2 + x + 5) \cdot 1 (加法通过乘以常数 1 转化为乘法约束)

约束 1: xx=v1x \cdot x = v_1

  • 左输入 (AA): 取 xx (向量第 3 位)。a1=[0,0,1,0,0]\mathbf{a}_1 = [0, 0, 1, 0, 0]
  • 右输入 (BB): 取 xx (向量第 3 位)。b1=[0,0,1,0,0]\mathbf{b}_1 = [0, 0, 1, 0, 0]
  • 输出 (CC): 取 v1v_1 (向量第 4 位)。c1=[0,0,0,1,0]\mathbf{c}_1 = [0, 0, 0, 1, 0]
  • 验证: (1x)×(1x)=v1(1\cdot x) \times (1\cdot x) = v_1

约束 2: v1x=v2v_1 \cdot x = v_2

  • 左输入 (AA): 取 v1v_1a2=[0,0,0,1,0]\mathbf{a}_2 = [0, 0, 0, 1, 0]
  • 右输入 (BB): 取 xxb2=[0,0,1,0,0]\mathbf{b}_2 = [0, 0, 1, 0, 0]
  • 输出 (CC): 取 v2v_2c2=[0,0,0,0,1]\mathbf{c}_2 = [0, 0, 0, 0, 1]
  • 验证: (1v1)×(1x)=v2(1\cdot v_1) \times (1\cdot x) = v_2

约束 3: (v2+x+5)1=out(v_2 + x + 5) \cdot 1 = out

这是一次利用 R1CS 特性处理加法的操作。

  • 左输入 (AA): 线性组合 v2+x+5v_2 + x + 5
    • 5 对应常数项 (5×s[0]5 \times \mathbf{s}[0])。
    • a3=[5,0,1,0,1]\mathbf{a}_3 = [5, 0, 1, 0, 1]
  • 右输入 (BB): 常数 1。
    • b3=[1,0,0,0,0]\mathbf{b}_3 = [1, 0, 0, 0, 0]
  • 输出 (CC): 取 outout
    • c3=[0,1,0,0,0]\mathbf{c}_3 = [0, 1, 0, 0, 0]
  • 验证: (5+x+v2)×1=out(5 + x + v_2) \times 1 = out

2.4 矩阵形式 (Matrix Representation)

将所有约束的向量堆叠起来,形成三个矩阵 A,B,CA, B, C。 假设电路有 mm 个门,解向量长度为 nn。矩阵维度为 m×nm \times n

A=[001000001050101],B=[001000010010000],C=[000100000101000]A = \begin{bmatrix} 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 \\ 5 & 0 & 1 & 0 & 1 \end{bmatrix}, \quad B = \begin{bmatrix} 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 \end{bmatrix}, \quad C = \begin{bmatrix} 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 1 \\ 0 & 1 & 0 & 0 & 0 \end{bmatrix}

R1CS 系统定义: 对于给定的公开输入和私有 Witness 组成的向量 s\mathbf{s},如果它是合法的,则必须满足:

(As)(Bs)=(Cs)(A \cdot \mathbf{s}) \circ (B \cdot \mathbf{s}) = (C \cdot \mathbf{s})

(\circ 代表阿达玛乘积,即逐元素相乘)

2.5 总结

  • 输入:算术电路图。
  • 输出:三个稀疏矩阵 A,B,CA, B, C
  • 意义:将复杂的程序逻辑,彻底转化为了线性代数问题(矩阵运算)。
  • 下一步:矩阵是离散的,为了能进行极简验证(Probabilistic Check),我们需要通过多项式插值将其转化为连续形式(QAP)。

第三节:R1CS 到 QAP (R1CS to QAP)

3.1 核心目标

R1CS 虽然严谨,但验证成本很高:如果有 100 万个约束(矩阵有 100 万行),验证者就需要逐行检查。 QAP (Quadratic Arithmetic Program) 的目标是将这 mm 个离散的约束方程,打包成 1 个连续的多项式方程

变换逻辑

  • 矩阵的行 (Rows) \rightarrow 多项式的求值点 (Evaluation Points)
  • 矩阵的列 (Columns) \rightarrow 基多项式 (Basis Polynomials)

3.2 拉格朗日插值 (Lagrange Interpolation)

这是实现变换的数学工具。它的作用是找到一个多项式,使其穿过所有指定的坐标点。

变换过程

  1. 定义根 (Roots):为 R1CS 的每一行(每一个约束门)分配一个索引值 rkr_k
    • 理论上:可以是 {1,2,3,,m}\{1, 2, 3, \dots, m\}
    • 工程上:为了利用 FFT 加速,使用 单位根 (Roots of Unity) {1,ω,ω2,,ωm1}\{1, \omega, \omega^2, \dots, \omega^{m-1}\}
  2. 列变换: 对于矩阵 AA 的每一列 ii(对应第 ii 个变量),我们构造一个多项式 ui(x)u_i(x),使得在第 kk 行,多项式的值等于矩阵该位置的元素值。 ui(rk)=Ak,iu_i(r_k) = A_{k,i} 同理构造 vi(x)v_i(x) 对应矩阵 BBwi(x)w_i(x) 对应矩阵 CC

3.3 构造 QAP 多项式

一旦有了基多项式 {ui(x),vi(x),wi(x)}\{u_i(x), v_i(x), w_i(x)\},我们将解向量 s\mathbf{s} 中的数值(Witness)作为系数进行线性组合。

定义三个全局多项式:

U(x)=i=0nsiui(x)U(x) = \sum_{i=0}^n s_i \cdot u_i(x)
V(x)=i=0nsivi(x)V(x) = \sum_{i=0}^n s_i \cdot v_i(x)
W(x)=i=0nsiwi(x)W(x) = \sum_{i=0}^n s_i \cdot w_i(x)
  • 含义U(x)U(x) 代表了在任意 xx 点,电路左输入导线的加权总和。

3.4 目标多项式 t(x)t(x) (The Target Polynomial)

这是一个系统预设的公有多项式,它的根就是所有约束行的索引。 如果使用单位根,它具有极其简洁的形式:

t(x)=k=0m1(xωk)=xm1t(x) = \prod_{k=0}^{m-1} (x - \omega^k) = x^m - 1

它的零点恰好就是我们需要检查约束的所有位置。

3.5 QAP 核心方程与整除性

这是整个转换的终点。 如果 Witness s\mathbf{s} 是正确的,那么在所有的约束点 x{r0,,rm1}x \in \{r_0, \dots, r_{m-1}\} 上,都有:

U(x)V(x)W(x)=0U(x) \cdot V(x) - W(x) = 0

根据因式定理 (Factor Theorem),如果多项式 P(x)=U(x)V(x)W(x)P(x) = U(x)V(x) - W(x) 在所有根处都为 0,那么它一定能被 t(x)t(x) 整除。 因此,必然存在一个商多项式 H(x)H(x) 使得:

U(x)V(x)W(x)=H(x)t(x)U(x) \cdot V(x) - W(x) = H(x) \cdot t(x)

3.6 为什么叫“二次” (Quadratic)?

虽然多项式的阶数(Degree)很高(取决于电路规模 mm),但约束关系的代数结构是: (线性组合)×(线性组合)(线性组合)=0(\text{线性组合}) \times (\text{线性组合}) - (\text{线性组合}) = 0 这在代数定义上属于二次约束 (Quadratic Constraint)。这是双线性配对(只能做一次乘法)所能验证的极限。

3.7 总结

  • 输入:R1CS 矩阵和解向量 s\mathbf{s}
  • 输出:一组多项式 U(x),V(x),W(x),H(x)U(x), V(x), W(x), H(x) 和目标 t(x)t(x)
  • 核心优势: 验证者不再需要检查 100 万个方程。根据 Schwartz-Zippel 引理,验证者只需要随机挑选一个点 τ\tau,检查 U(τ)V(τ)W(τ)=?H(τ)t(τ)U(\tau)V(\tau) - W(\tau) \overset{?}{=} H(\tau)t(\tau) 是否成立。
    • 如果在这个随机点成立,那么整个多项式在全域相等的概率接近 100%。
    • 这就是 zk-SNARKs "Succinct"(简洁)特性的来源。

第四节:Groth16 原理 (Groth16 Protocol over QAP)

4.1 核心数学工具

Groth16 建立在椭圆曲线对 (Elliptic Curve Pairings) 之上。

  • 群结构: 定义三个循环群 G1,G2,GTG_1, G_2, G_T,阶为质数 rr。生成元分别为 gG1,hG2g \in G_1, h \in G_2
  • 符号: [x]1=xg[x]_1 = x \cdot g, [x]2=xh[x]_2 = x \cdot h
  • 双线性配对 (Bilinear Pairing): 映射 e:G1×G2GTe: G_1 \times G_2 \rightarrow G_T
    • 关键性质: e([a]1,[b]2)=e([1]1,[1]2)abe([a]_1, [b]_2) = e([1]_1, [1]_2)^{ab}。这使得我们可以验证加密数据的乘法关系。

4.2 第一步:可信设置 (Trusted Setup)

为了生成 CRS (Common Reference String),必须采样一组随机数(有毒废料),并在生成密钥后销毁。

随机采样: α,β,γ,δ,τFr\alpha, \beta, \gamma, \delta, \tau \in \mathbb{F}_r^*

生成密钥:

  1. 验证密钥 (vkvk): 用于处理公开输入和最终检查。

    vk=([α]1,[β]2,[γ]2,[δ]2,{[βui(τ)+αvi(τ)+wi(τ)γ]1}i=0l)vk = \left( [\alpha]_1, [\beta]_2, [\gamma]_2, [\delta]_2, \{ [\frac{\beta u_i(\tau) + \alpha v_i(\tau) + w_i(\tau)}{\gamma}]_1 \}_{i=0}^l \right)
    • 最后一项是针对公开输入 (i=0li=0 \dots l) 的预计算。
  2. 证明密钥 (pkpk): 必须包含生成 A,B,CA, B, C 所需的所有群元素。

    pk=([α]1,[β]1,[δ]1G1 常数,{[τi]1}i=0d1G1 幂次,{[βui(τ)+αvi(τ)+wi(τ)δ]1}i=l+1mG1 私有 Witness,{[τit(τ)δ]1}i=0d2G1 H项,[β]2,[δ]2G2 常数,{[τi]2}i=0d1G2 幂次)pk = \left( \underbrace{[\alpha]_1, [\beta]_1, [\delta]_1}_{\text{G1 常数}}, \underbrace{\{ [\tau^i]_1 \}_{i=0}^{d-1}}_{\text{G1 幂次}}, \underbrace{\{ [\frac{\beta u_i(\tau) + \alpha v_i(\tau) + w_i(\tau)}{\delta}]_1 \}_{i=l+1}^m}_{\text{G1 私有 Witness}}, \underbrace{\{ [\frac{\tau^i t(\tau)}{\delta}]_1 \}_{i=0}^{d-2}}_{\text{G1 H项}}, \underbrace{[\beta]_2, [\delta]_2}_{\text{G2 常数}}, \underbrace{\{ [\tau^i]_2 \}_{i=0}^{d-1}}_{\text{G2 幂次}} \right)
    • 注: pkpk 必须包含 [β]1[\beta]_1{[τi]2}\{[\tau^i]_2\} 才能完成 CCBB 的计算。

4.3 第二步:证明生成 (Proving)

证明者拥有 Witness a={a0,,am}\mathbf{a} = \{a_0, \dots, a_m\},满足 QAP 方程 U(x)V(x)W(x)=h(x)t(x)U(x)V(x) - W(x) = h(x)t(x)。 为了实现零知识,证明者采样随机数 r,sFrr, s \in \mathbb{F}_r

构造 π=([A]1,[B]2,[C]1)\pi = ([A]_1, [B]_2, [C]_1):

  1. 计算 AA (G1G_1 群):

    A=[α]1+i=0maiui(τ)[1]1+r[δ]1A = [\alpha]_1 + \sum_{i=0}^m a_i u_i(\tau)[1]_1 + r[\delta]_1
    • 意义: 对 U(τ)U(\tau) 的加密评估,加上 α\alpha 偏移和 rr 混淆。
  2. 计算 BB (G2G_2 群):

    B=[β]2+i=0maivi(τ)[1]2+s[δ]2B = [\beta]_2 + \sum_{i=0}^m a_i v_i(\tau)[1]_2 + s[\delta]_2
    • 意义: 对 V(τ)V(\tau) 的加密评估,加上 β\beta 偏移和 ss 混淆。
  3. 计算 CC (G1G_1 群 - 核心推导): 为了满足配对等式,需根据 ABA \cdot B 的展开式构造 CC

    C=i=l+1mai[βui(τ)+αvi(τ)+wi(τ)δ]1+[h(τ)t(τ)δ]1利用 pk 计算私有部分+As+Brrsδ平衡随机项C = \underbrace{\sum_{i=l+1}^m a_i \left[\frac{\beta u_i(\tau) + \alpha v_i(\tau) + w_i(\tau)}{\delta}\right]_1 + \left[\frac{h(\tau)t(\tau)}{\delta}\right]_1}_{\text{利用 pk 计算私有部分}} + \underbrace{As + Br - rs\delta}_{\text{平衡随机项}}
    • 注意: 计算 BrBr 时需要用到 pkpk 中的 [β]1[\beta]_1 以及 G1G_1 上的 [V(τ)]1[V(\tau)]_1

4.4 第三步:验证 (Verification)

验证者接收 π=(A,B,C)\pi = (A, B, C) 和公开输入 apub={a1,,al}\mathbf{a}_{pub} = \{a_1, \dots, a_l\}

  1. 计算公开输入项 [Lpub]1[L_{pub}]_1:

    [Lpub]1=i=0lai[βui(τ)+αvi(τ)+wi(τ)γ]1[L_{pub}]_1 = \sum_{i=0}^l a_i \left[ \frac{\beta u_i(\tau) + \alpha v_i(\tau) + w_i(\tau)}{\gamma} \right]_1
  2. 执行配对检查:

    e(A,B)=?e(α,β)e(Lpub,γ)e(C,δ)e(A, B) \overset{?}{=} e(\alpha, \beta) \cdot e(L_{pub}, \gamma) \cdot e(C, \delta)

4.5 严谨性推导:为什么等式成立?

将验证等式右边 (RHS) 的指数部分展开,证明其等于 ABA \cdot B

RHS 展开:

RHS=αβ+Lpubγ+Cδ\text{RHS} = \alpha\beta + L_{pub}\gamma + C\delta

代入 LpubL_{pub}CC 的定义(忽略群同构,看作代数):

=αβ+i=0lai(βui+αvi+wi)公开部分+(i=l+1mai(βui+αvi+wi)+ht)私有部分+δ(As+Brrsδ)= \alpha\beta + \underbrace{\sum_{i=0}^l a_i(\beta u_i + \alpha v_i + w_i)}_{\text{公开部分}} + \underbrace{\left( \sum_{i=l+1}^m a_i(\beta u_i + \alpha v_i + w_i) + ht \right)}_{\text{私有部分}} + \delta(As + Br - rs\delta)

合并公开和私有部分的求和 i=0m\sum_{i=0}^m

=αβ+(βU(τ)+αV(τ)+W(τ))+h(τ)t(τ)+δ(As+Brrsδ)= \alpha\beta + (\beta U(\tau) + \alpha V(\tau) + W(\tau)) + h(\tau)t(\tau) + \delta(As + Br - rs\delta)

利用 QAP 核心关系 W+ht=UVW + ht = UV

=αβ+βU+αV+UV+δ(As+Brrsδ)= \alpha\beta + \beta U + \alpha V + UV + \delta(As + Br - rs\delta)

这正是 AB=(α+U+rδ)(β+V+sδ)A \cdot B = (\alpha + U + r\delta)(\beta + V + s\delta) 的展开形式。

4.6 参数作用总结

  • τ\tau: 多项式求值的秘密坐标,保证计算基于 QAP。
  • α,β\alpha, \beta: 强制 AABB 包含特定的线性结构,确保 UVUV 乘积被正确构造。
  • γ\gamma: 绑定公开输入。验证者必须使用 vkvk 中的 γ\gamma 项,无法篡改 Public Input。
  • δ\delta: 绑定私有 Witness。证明者必须使用 pkpk 中的 δ\delta 项构造 CC,无法篡改 Private Witness。
  • r,sr, s: 提供零知识性,使 A,BA, B 的分布随机化,隐藏真实的 U,VU, V 值。

第五节:实战演练——Keyless Account (Google OIDC) 认证原理

场景目标: 用户拥有一份由 Google 签发的 JWT (JSON Web Token),其中包含 Google 的 RSA 签名。用户想向区块链合约证明:“我是这个 JWT 的拥有者,且这个 JWT 授权了当前交易使用的临时公钥 (pkephpk_{eph}),但我不想把 JWT 原始内容暴露在链上。”


5.1 初始状态:输入数据 (Inputs)

在将数据喂给 Groth16 之前,我们需要将业务数据转化为有限域 Fr\mathbb{F}_r 上的向量。

A. 解向量 (Witness Vector a\mathbf{a})

假设电路已经过 R1CS 转换。向量 a\mathbf{a} 包含所有输入变量:

a=[ apub  apriv ]\mathbf{a} = [\ \mathbf{a}_{pub} \ | \ \mathbf{a}_{priv} \ ]

1. 公开输入 (apub\mathbf{a}_{pub} - 链上合约已知):

  • a[0]=1\mathbf{a}[0] = 1 (常数项)
  • a[1]=Hash(pkeph,salt_commitment)\mathbf{a}[1] = \text{Hash}(pk_{eph}, \text{salt\_commitment}) (Nonce/临时公钥指纹)
  • a[2]=Hash(PKGoogle)\mathbf{a}[2] = \text{Hash}(PK_{Google}) (Google 公钥指纹)

2. 私有输入 (apriv\mathbf{a}_{priv} - 用户本地持有):

  • a[3k]\mathbf{a}[3 \dots k]: JWT Header & Payload (包含用户的 Google ID, iss, aud 等)
  • a[k+1m]\mathbf{a}[k+1 \dots m]: RSA Signature (Google 对 JWT 的 2048 位数字签名)
  • a[m+1]\mathbf{a}[m+1]: Salt (用户隐私盐值)

B. 电路逻辑 (The Circuit Logic)

这个电路包含成千上万个乘法门(主要是 RSA 模幂运算),但核心逻辑可以简化为两个超级约束:

  1. 验签约束: RSA_Verify(Payload,Signature,PKGoogle)==True\text{RSA\_Verify}(\text{Payload}, \text{Signature}, PK_{Google}) == \text{True}
  2. 绑定约束: Payload.nonce==Hash(pkeph,Salt)\text{Payload.nonce} == \text{Hash}(pk_{eph}, \text{Salt})

5.2 证明生成 (Prover's Computation - Off-Chain)

用户在本地(浏览器/App)生成证明。他拥有 pkpk (Proving Key) 和完整的 Witness a\mathbf{a}。 为了实现零知识(隐藏 Signature 和 Salt),用户选择随机数 r,sFrr, s \in \mathbb{F}_r

1. 计算多项式快照

用户将 a\mathbf{a} 代入基多项式,在秘密点 τ\tau 处求值(实际上是通过 pkpk 中的椭圆曲线点进行线性组合):

  • UJWT(τ)U_{JWT}(\tau): 代表 JWT 数据和 RSA 签名的多项式总和。
  • VJWT(τ)V_{JWT}(\tau): 对应于电路逻辑的右输入总和。
  • WJWT(τ)W_{JWT}(\tau): 对应于电路逻辑的输出总和。

逻辑正确性保证: 因为用户的 JWT 签名是真的,且 Nonce 匹配,所以必定存在商多项式 H(x)H(x) 使得: UJWT(τ)VJWT(τ)WJWT(τ)=H(τ)t(τ)U_{JWT}(\tau) \cdot V_{JWT}(\tau) - W_{JWT}(\tau) = H(\tau)t(\tau)

2. 生成加密点 A,B,CA, B, C

用户利用 pkpk 计算出三个混淆后的点:

  • 计算 AA (在 G1G_1): A=[α]1+[UJWT(τ)]1+r[δ]1A = [\alpha]_1 + [U_{JWT}(\tau)]_1 + r[\delta]_1

    • 隐喻: 这里面包含了用户的 RSA 签名信息,但被随机数 rr 和系统参数 α\alpha 彻底打乱了。
  • 计算 BB (在 G2G_2): B=[β]2+[VJWT(τ)]2+s[δ]2B = [\beta]_2 + [V_{JWT}(\tau)]_2 + s[\delta]_2

    • 隐喻: 电路验证逻辑的另一半,同样被随机数 ss 混淆。
  • 计算 CC (在 G1G_1 - 核心粘合剂): C=1δ(Privatepart(βU+αV+W)+H(τ)t(τ))+(As+Brrsδ)C = \frac{1}{\delta} \left( \text{Private}_{part}(\beta U + \alpha V + W) + H(\tau)t(\tau) \right) + (As + Br - rs\delta)

    • 隐喻: CC 包含了所有私有数据的校验和,以及商 HH。它是为了让等式平衡而构造的“补丁”。

输出: 用户发送 π=(A,B,C)\pi = (A, B, C) 和公开的 pkephpk_{eph} 到链上。


5.3 链上验证 (Verifier's Computation - On-Chain Contract)

智能合约(Verifier)不知道 JWT 长什么样,也不知道 Google 签名是多少。它只有 vkvk 和用户提交的 pkephpk_{eph}

1. 构建公开挑战 (LpubL_{pub})

合约根据接收到的 pkephpk_{eph} 和写死在合约里的 Google 公钥指纹,计算公开输入的线性组合:

[Lpub]1=1[IC0]+Hash(pkeph)[IC1]+Hash(PKGoogle)[IC2][L_{pub}]_1 = 1 \cdot [\text{IC}_0] + \text{Hash}(pk_{eph}) \cdot [\text{IC}_1] + \text{Hash}(PK_{Google}) \cdot [\text{IC}_2]

(注: ICi\text{IC}_ivkvk 中预计算好的 βui+αvi+wiγ\frac{\beta u_i + \alpha v_i + w_i}{\gamma})

2. 最终配对检查 (The Pairing Check)

合约执行以下指令(消耗约 200k Gas):

e(A,B)=?e(α,β)e(Lpub,γ)e(C,δ)e(A, B) \overset{?}{=} e(\alpha, \beta) \cdot e(L_{pub}, \gamma) \cdot e(C, \delta)

5.4 深度解析:为什么这个等式能确权?

让我们看看如果用户作弊会发生什么,以此反推验证的有效性。

情景一:用户拿了一个伪造的 JWT(签名不对)

  1. 在电路运算中,RSA 模幂约束无法满足。
  2. 导致 UVWU \cdot V - W 产生了一个无法被 t(x)t(x) 整除的余数 R(x)R(x)
  3. 用户在计算 CC 时,无法消除分母中的 δ\delta(因为余数项没法除尽)。
  4. 为了强行生成 CC,用户只能瞎填一个点。
  5. 链上验算时:e(C,δ)e(C, \delta) 产生的值将无法填补 e(A,B)e(A, B) 展开后的缺口。
  6. 结果:验证失败。

情景二:用户拿了别人的真实 JWT(偷来的),但想绑定自己的 Key

  1. JWT 签名是真的,RSA 约束通过。
  2. 但是 Nonce 绑定约束 失败:
    JWT.nonceHash(pkuser_fake,salt)\text{JWT.nonce} \neq \text{Hash}(pk_{user\_fake}, \text{salt})
  3. 同样导致多项式无法整除。
  4. 结果:验证失败。

情景三:用户想根据 A,BA, B 反推 Google 签名

  1. A=α+U+rδA = \alpha + U + r\delta
  2. 由于 rr 是用户本地生成的随机数,对于链上观察者来说,AA 只是一个随机点。
  3. 根据离散对数难题,无法从点 AA 还原出数值 UU
  4. 结果:隐私 100% 安全。

5.5 总结:Groth16 在 Keyless 中的数据流

步骤数据状态可见性含义
InputJWT_String, Signature, SaltPrivate (用户本地)原始凭证
CircuitRSA_Check, Nonce_CheckPublic (电路逻辑)验证规则
ProofA,B,CA, B, C (三个椭圆曲线点)Public (链上)"我知道凭证"的数学证据
Verifye(A,B)=e(A,B) = \dotsPublic (合约执行)零知识放行

通过 Groth16,区块链合约得以在看不见 Google 签名的情况下,确信 Google 确实为该用户签发了证书。