在去中心化人工智能(Decentralized AI)的前沿阵地,我们始终在与一个核心难题角力:如何在不泄露模型专有参数(或用户隐私输入)的前提下,向链上智能合约数学证明某项推理确实由指定的轻量级神经网络计算得出,且中间过程未遭任何投毒与篡改?
这正是 zk-ML(零知识机器学习,Zero-Knowledge Machine Learning)的用武之地。然而,从成熟的深度学习框架(如 PyTorch、ONNX)跨越到密码学证明系统(如 Groth16、Plonk),横亘着一道巨大的底层鸿沟——神经网络运行在连续的高精度浮点数世界,而零知识证明系统则建立在离散有限域(Finite Field)的算术电路(Arithmetic Circuit)之上。
本文将剥离所有浮于表面的概念包装,深入到 Circom 与 R1CS(Rank-1 Constraint System,一阶约束系统)的最底层,拆解如何将一个轻量级全连接神经网络(包含线性变换与激活函数)完整转化为一组数学约束方程。
一、从浮点矩阵到有限域定点数映射(Fixed-Point Quantization)
在标准 EVM 与 zk-SNARK 有限域(例如以太坊常用的 BN254 椭圆曲线标量域 $\mathbb{F}_p$,其中素数模数 $p = 21888242871839275222246405745257275088548364400416034343698204186575808495617$)中,根本不存在小数与 IEEE 754 浮点数表示。
如果直接在有限域内进行除法,得到的是模逆元,这会彻底破坏数值的大小关系和线性性质。因此,进入零知识电路的第一道工序就是量化与定点化(Quantization & Scaling)。
1.1 定点化缩放因子原理
假设我们选择缩放精度基数 $S = 2^m$(在轻量级模型中通常取 $m=16$,即 $S = 65536$):
- 任意浮点权重 $W \in \mathbb{R}$ 与输入特征 $X \in \mathbb{R}$,首先被映射为量化整数:
$$W_{\text{quant}} = \lfloor W \cdot S \rceil, \quad X_{\text{quant}} = \lfloor X \cdot S \rceil$$ - 当执行矩阵乘法加权求和 $Y = W \cdot X$ 时,乘积项的量化值为:
$$Y_{\text{quant}} = W_{\text{quant}} \cdot X_{\text{quant}} = \lfloor W \cdot S \rceil \cdot \lfloor X \cdot S \rceil \approx (W \cdot X) \cdot S^2$$ - 为了让输出维度与下一层的输入对齐,必须除以缩放因子 $S$,将数值规模重整回单倍放大:
$$Y_{\text{aligned}} = \lfloor \frac{Y_{\text{quant}}}{S} \rfloor$$
在密码学有限域中,负数以补码(模 $p$ 减法)呈现。因此,必须在电路中对数值范围(Range Check)施加严格约束,防止大正数溢出翻转为有限域的极大负数表示。
二、算术电路门(Arithmetic Gates)与 R1CS 数学本质
在 zk-SNARK 系统中,计算过程必须被拍平为只包含加法门和乘法门的有向无环图(DAG),最终转化为 R1CS 形式。
2.1 什么是 R1CS 约束?
一个 R1CS 约束由三个系数向量构成的线性组合矩阵乘积定义:
$$(A \cdot s) \times (B \cdot s) = (C \cdot s)$$
其中:
- $s$ 是完整的证明见证向量(Witness Vector),包含公共输入、私有输入、模型权重以及所有中间计算节点:
$$s = [1, x_1, x_2, \dots, w_1, w_2, \dots, y_1, y_2, \dots]^T$$ - $A, B, C$ 是稀疏系数矩阵。
- 关键限制:每个独立的 R1CS 约束方程中,最多只能包含一次乘法操作。加法运算是免费的(可以通过线性组合合并在同一个乘法门的向量系数中),但每一次非线性的乘法、位分解或条件判断,都会消耗一个或多个约束门。
三、Circom 神经元电路实现:全连接层与 ReLU 激活
我们用 Circom 2.1 手写一个包含定点缩放、矩阵乘法累加、以及非线性激活函数 ReLU 的标准神经元电路组件。
3.1 神经元线性加权求和组件
在以下代码中,我们定义了一个具有 $N$ 个输入的密集连接神经元。由于乘法必须逐一受到有限域方程约束,我们显式声明加乘逻辑:
pragma circom 2.1.6; // 线性全连接加权累加电路 template DenseLinear(nInputs, scale) { signal input in[nInputs]; // 输入特征向量 (定点化放大 scale 倍) signal input weights[nInputs]; // 模型权重向量 (定点化放大 scale 倍) signal input bias; // 偏置向量 (定点化放大 scale 倍) signal output out; // 线性输出 (已消除多余的 scale 放大) // 中间乘积累加信号 signal mulResults[nInputs]; signal accum[nInputs + 1]; accum[0] <== bias * scale; // 偏置对齐到 scale^2 维度 for (var i = 0; i < nInputs; i++) { // 约束 1: 逐项乘法门 (消耗 1 个 R1CS 约束) mulResults[i] <== in[i] * weights[i]; // 线性累加 (加法合并在系数中,不消耗独立乘法门) accum[i + 1] <== accum[i] + mulResults[i]; } // 缩放还原:需要断言 accum[nInputs] == out * scale + remainder signal remainder; // 见证生成 (非确定性赋值) out <-- accum[nInputs] \ scale; remainder <-- accum[nInputs] % scale; // 约束 2: 严格数学等式约束,杜绝证明者伪造商与余数 accum[nInputs] === out * scale + remainder; // 约束 3: 余数范围检查,确保 0 <= remainder < scale component rangeCheck = RangeCheck(16); rangeCheck.in <== remainder; } // 基础 16 位正数范围检查模板 template RangeCheck(nBits) { signal input in; signal bits[nBits]; var sum = 0; for (var i = 0; i < nBits; i++) { bits[i] <-- (in >> i) & 1; // 断言每一位只能是 0 或 1 (消耗 1 个 R1CS 约束) bits[i] * (bits[i] - 1) === 0; sum += bits[i] * (1 << i); } in === sum; }3.2 非线性 ReLU 激活函数的电路转换
神经网络的核心是非线性激活函数 $\text{ReLU}(x) = \max(0, x)$。在传统 CPU 上,这只是一行条件分支指令;但在算术电路中,没有条件跳转(if-else),必须利用阶跃比较门与位分解构造约束:
// ReLU 激活电路模板 template ReLU() { signal input in; signal output out; // 辅助布尔信号:判断 in 是否大于等于 0 signal isPositive; // 引入小于比较器判断正负 (假设数值经过偏置偏移在安全范围内) component comp = LessThan(64); // 将有符号数偏移到正数区间进行比较 comp.in[0] <== in; comp.in[1] <== 1 << 63; // 符号位判定基准 isPositive <== 1 - comp.out; // 约束 1: isPositive 必须是布尔值 isPositive * (1 - isPositive) === 0; // 约束 2: 激活输出值由开关乘法决定 out <== isPositive * in; } // 64 位无符号比较器 template LessThan(n) { signal input in[2]; signal output out; component n2b = Num2Bits(n + 1); n2b.in <== in[0] + (1 << n) - in[1]; out <== 1 - n2b.out[n]; } template Num2Bits(n) { signal input in; signal output out[n]; var accum = 0; for (var i = 0; i < n; i++) { out[i] <-- (in >> i) & 1; out[i] * (1 - out[i]) === 0; accum += out[i] * (1 << i); } in === accum; }四、约束数量分析与链上验证开销权衡
构建 zk-ML 电路时,工程师必须时刻对“约束总数(Total R1CS Constraints)”保持极度敏感:
| 神经网络操作 | 传统计算复杂度 | Circom 电路约束数(单操作) | 性能开销核心原因 |
|---|---|---|---|
| 矩阵线性加权 | $O(N)$ 乘法 | $N + 16$ 门 | 每个权重乘法占 1 门,缩放除法余数范围检查占 16 门 |
| ReLU 激活 | $O(1)$ 指令 | 约 66 门 | 需要拆解 64 位二进制位以验证符号与比较逻辑 |
| Softmax 归一化 | $O(N)$ 指数运算 | 超 20,000 门(拟合) | 指数与除法在有限域中极昂贵,通常移至链下由 ArgMax 替代 |
如果一个轻量级二分类神经网络包含 128 个隐藏层神经元,总约束数通常在15,000 到 30,000 个 R1CS 约束之间。在 Apple M 系列或现代服务器 CPU 上,Groth16 协议在 1.2 秒内即可完成证明生成(Proof Generation),生成的零知识证明体积仅为3 个群元素(约 128 字节)。
当把验证密钥(Verification Key)部署为 Solidity 智能合约后,以太坊主网通过预编译合约0x08(ecPairing双线性配对检查)校验该证明仅需消耗约210,000 到 230,000 Gas,真正实现了在链上对高维神经网络推理正确性的超低成本背书。
五、工程师避坑指南与总结
在实际工程落地 zk-ML 时,请牢记以下三条铁律:
- 警惕下溢与有限域回绕(Field Wraparound):必须严防负数或过大的中间累加值超出有限域标量上限。如果不做全局截断限制,恶意的证明者可以通过注入巨大的有限域元素绕过数值断言。
- 拒绝在电路中执行浮点激活:千万不要尝试在电路中精确还原 $e^x$(Sigmoid)或复杂的 GELU。工业界标准做法是采用多项式泰勒展开近似(如平方激活 $x^2$),或者使用硬件友好的阶梯量化激活。
- 见证生成与约束断言的严格分离:使用
<--赋值的私有变量,后续必须无一例外地使用===进行完备的代数等式约束。缺少约束的赋值是智能合约零知识漏洞最普遍的温床。