Python中的零知识证明:用于验证模型所有权与计算完整性
各位听众,大家好。今天我们来探讨一个新兴且强大的密码学技术:零知识证明(Zero-Knowledge Proofs, ZKPs),以及它在验证模型所有权和计算完整性方面的应用。我们将主要使用Python语言,并结合相关库来演示ZKPs的实际应用。
1. 什么是零知识证明?
零知识证明是一种协议,允许一方(Prover,证明者)向另一方(Verifier,验证者)证明一个陈述是真实的,而无需透露除陈述本身之外的任何信息。换句话说,Verifier确信Prover知道某个秘密或拥有某个属性,但Verifier无法学到关于该秘密或属性的任何信息。
零知识证明需要满足三个关键性质:
- 完整性(Completeness): 如果陈述是真实的,诚实的Prover总是能够说服诚实的Verifier相信。
- 可靠性(Soundness): 如果陈述是虚假的,没有Prover能够说服诚实的Verifier相信(除了以极小的概率)。
- 零知识性(Zero-Knowledge): 在协议的交互过程中,Verifier除了知道陈述是真实的之外,无法获得任何其他信息。
2. 零知识证明的原理:以 Schnorr 协议为例
Schnorr 协议是一个经典的零知识证明协议,用于证明Prover知道离散对数。让我们通过一个例子来说明其工作原理。
假设有一个公开的素数 p 和一个生成元 g,以及一个公开的 y = gx mod p,其中 x 是 Prover 的秘密。Prover 想要向 Verifier 证明她知道 x,而无需透露 x 的具体值。
Schnorr 协议的步骤如下:
-
Prover:
- 选择一个随机数 k。
- 计算 r = gk mod p。
- 将 r 发送给 Verifier。
-
Verifier:
- 选择一个随机数 c。
- 将 c 发送给 Prover。
-
Prover:
- 计算 s = k + cx mod (p-1)。
- 将 s 发送给 Verifier。
-
Verifier:
- 验证 gs mod p = r * yc mod p 是否成立。
如果等式成立,Verifier 确信 Prover 知道 x。
Python 代码示例 (Schnorr 协议):
import random
def schnorr_protocol():
# 公共参数
p = 23 # 素数
g = 2 # 生成元
# Prover 的秘密
x = random.randint(1, p - 1)
# 计算公开的 y
y = pow(g, x, p)
# --- Prover 的操作 ---
# 1. 选择随机数 k 并计算 r
k = random.randint(1, p - 1)
r = pow(g, k, p)
# 2. 将 r 发送给 Verifier
print(f"Prover sends r = {r} to Verifier")
# --- Verifier 的操作 ---
# 1. 选择随机数 c
c = random.randint(1, p - 1)
# 2. 将 c 发送给 Prover
print(f"Verifier sends c = {c} to Prover")
# --- Prover 的操作 ---
# 1. 计算 s
s = (k + c * x) % (p - 1)
# 2. 将 s 发送给 Verifier
print(f"Prover sends s = {s} to Verifier")
# --- Verifier 的操作 ---
# 1. 验证等式
left = pow(g, s, p)
right = (r * pow(y, c, p)) % p
if left == right:
print("Verifier: Proof accepted!")
return True
else:
print("Verifier: Proof rejected!")
return False
# 运行 Schnorr 协议
schnorr_protocol()
代码解释:
p和g是公共参数。x是 Prover 的秘密,y是基于x计算出的公开值。- Prover 选择随机数
k并计算r。 - Verifier 选择随机数
c。 - Prover 计算
s并将其发送给 Verifier。 - Verifier 验证
g<sup>s</sup> mod p = r * y<sup>c</sup> mod p是否成立。
3. 零知识证明的类型
零知识证明可以分为多种类型,常见的包括:
- 交互式零知识证明 (Interactive Zero-Knowledge Proofs): 需要 Prover 和 Verifier 之间进行多轮交互,例如 Schnorr 协议。
- 非交互式零知识证明 (Non-Interactive Zero-Knowledge Proofs, NIZK): Prover 生成一个证明,Verifier 可以独立验证,无需交互。Fiat-Shamir 变换是一种常用的将交互式证明转换为非交互式证明的技术。
- 简洁非交互式知识论证 (Succinct Non-Interactive ARguments of Knowledge, SNARKs): 一种非常强大的 NIZK,具有证明大小小、验证速度快等优点,但通常需要复杂的设置阶段。
- Bulletproofs: 另一种 NIZK,不需要可信设置,并且在证明大小上具有对数复杂度。
- zk-STARKs: 一种透明的(无需可信设置)SNARK,它使用抗碰撞哈希函数来实现安全性,并且具有快速的证明生成和验证速度。
4. 零知识证明在模型所有权验证中的应用
在机器学习领域,模型所有权验证是一个重要的问题。攻击者可能窃取或复制他人的模型,并声称自己拥有该模型。零知识证明可以用于在不暴露模型细节的情况下验证模型的所有权。
一种方法是,模型所有者可以预先计算一些模型的属性(例如,特定输入对应的输出),并将这些属性作为公开信息。当有人声称拥有该模型时,所有者可以要求对方提供关于这些属性的零知识证明。如果对方能够提供有效的证明,则可以认为对方拥有该模型。
示例:使用哈希函数和 Merkle 树进行模型所有权验证
-
模型所有者:
- 选择一组输入数据 X。
- 计算模型在 X 上的输出 Y = Model(X)。
- 对 Y 中的每个元素计算哈希值 H(Y)。
- 构建一个 Merkle 树,其中叶子节点是 H(Y)。
- 公布 Merkle 树的根哈希值。
-
验证者(模型所有者):
- 选择 Y 中的一个元素 yi。
- 要求声称拥有模型的人提供 yi 对应的哈希值 hi = H(yi),以及 Merkle 树的证明路径(从 hi 到根哈希值)。
-
声称拥有模型的人:
- 使用自己的模型计算 X 上的输出 Y’。
- 找到 yi‘ (对应于 yi)。
- 计算 hi‘ = H(yi‘)。
- 提供 hi‘ 以及 Merkle 树的证明路径。
-
验证者:
- 验证 hi‘ 是否与 Merkle 树的证明路径一致,并且可以推导出正确的根哈希值。
- 验证 hi‘ 是否等于 H(yi) (模型所有者预先计算的哈希值)。
如果上述验证都通过,则可以认为声称拥有模型的人的确拥有与原始模型输出相似的模型。
Python 代码示例 (Merkle 树):
import hashlib
class MerkleTree:
def __init__(self, data):
self.data = data
self.leaves = [hashlib.sha256(str(d).encode('utf-8')).hexdigest() for d in data]
self.tree = self._build_tree(self.leaves)
def _build_tree(self, nodes):
if len(nodes) == 1:
return nodes
new_nodes = []
for i in range(0, len(nodes), 2):
if i + 1 < len(nodes):
combined = nodes[i] + nodes[i+1]
new_nodes.append(hashlib.sha256(combined.encode('utf-8')).hexdigest())
else:
new_nodes.append(nodes[i]) # If odd number of nodes, propagate the last one
return self._build_tree(new_nodes)
def get_root(self):
return self.tree[0]
def get_proof(self, index):
proof = []
nodes = self.leaves
while len(nodes) > 1:
new_nodes = []
for i in range(0, len(nodes), 2):
if i + 1 < len(nodes):
if i == index:
proof.append(nodes[i+1])
elif i + 1 == index:
proof.append(nodes[i])
combined = nodes[i] + nodes[i+1]
new_nodes.append(hashlib.sha256(combined.encode('utf-8')).hexdigest())
else:
new_nodes.append(nodes[i])
nodes = new_nodes
index //= 2 # Move up one level in the tree
return proof
def verify_proof(self, data, proof, root):
hash_val = hashlib.sha256(str(data).encode('utf-8')).hexdigest()
for sibling in proof:
if int(hash_val, 16) < int(sibling, 16): # Comparing hex strings as integers
combined = hash_val + sibling
else:
combined = sibling + hash_val
hash_val = hashlib.sha256(combined.encode('utf-8')).hexdigest()
return hash_val == root
# 示例使用
data = ['model output 1', 'model output 2', 'model output 3', 'model output 4']
merkle_tree = MerkleTree(data)
root = merkle_tree.get_root()
index_to_prove = 1 # Prove the second element
proof = merkle_tree.get_proof(index_to_prove)
data_to_prove = data[index_to_prove]
print(f"Root: {root}")
print(f"Proof for element at index {index_to_prove}: {proof}")
verified = merkle_tree.verify_proof(data_to_prove, proof, root)
if verified:
print("Proof is valid!")
else:
print("Proof is invalid!")
代码解释:
MerkleTree类实现了 Merkle 树的构建和验证。get_root()函数返回 Merkle 树的根哈希值。get_proof()函数返回指定索引的元素的证明路径。verify_proof()函数验证证明路径是否有效。
局限性:
这种方法依赖于模型输出的相似性,如果攻击者可以生成与原始模型输出非常相似的输出,但模型本身是不同的,则可能会绕过验证。更高级的零知识证明技术(例如 SNARKs)可以提供更强的安全性。
5. 零知识证明在计算完整性验证中的应用
零知识证明不仅可以用于验证模型所有权,还可以用于验证计算的完整性。这意味着 Prover 可以向 Verifier 证明某个计算的结果是正确的,而无需透露计算的具体过程或输入数据。
例如,一个 Prover 可以在一个私有数据集上训练一个机器学习模型,并使用零知识证明向 Verifier 证明该模型在某个公开数据集上的准确率高于某个阈值,而无需透露私有数据集或模型的具体参数。
示例:使用 zk-SNARKs 进行计算完整性验证
zk-SNARKs 是一种非常强大的零知识证明技术,可以用于证明任意计算的完整性。zk-SNARKs 的工作原理比较复杂,涉及到电路表示、多项式承诺、配对等概念。
使用 zk-SNARKs 的一般步骤如下:
- 将计算表示为电路: 将需要证明的计算转换为电路表示,例如算术电路。
- 生成证明密钥和验证密钥: 使用一个可信设置过程生成证明密钥和验证密钥。
- Prover 使用证明密钥生成证明: Prover 使用证明密钥和输入数据生成一个证明。
- Verifier 使用验证密钥验证证明: Verifier 使用验证密钥和公开的输入/输出数据验证证明。
Python 代码示例 (使用 Circom 和 SnarkJS):
由于 zk-SNARKs 的实现比较复杂,通常需要使用专门的工具,例如 Circom 和 SnarkJS。Circom 是一种用于定义电路的领域特定语言,SnarkJS 是一个用于生成和验证 zk-SNARKs 的 JavaScript 库。
以下是一个简单的示例,展示如何使用 Circom 和 SnarkJS 证明一个数的平方:
- 定义 Circom 电路 (square.circom):
pragma circom 2.0.0;
template Square() {
signal input in;
signal output out;
out <== in * in;
}
component main = Square();
- 编译 Circom 电路:
circom square.circom --r1cs --wasm --sym
- 生成证明密钥和验证密钥:
snarkjs powersOfTau new bn128 12 pot12_0000.ptau
snarkjs powersOfTau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v
snarkjs powersOfTau contribute pot12_0001.ptau pot12_0002.ptau --name="Second contribution" -v
snarkjs powersOfTau final pot12_0002.ptau pot12_final.ptau
snarkjs powersOfTau export_challenge pot12_final.ptau challenge.json
snarkjs powersOfTau import_challenge pot12_final.ptau challenge.json pot12_0003.ptau
snarkjs powersOfTau contribute pot12_0003.ptau pot12_0004.ptau --name="Third contribution" -v
snarkjs powersOfTau contribute pot12_0004.ptau pot12_0005.ptau --name="Forth contribution" -v
snarkjs powersOfTau final pot12_0005.ptau pot12_final_2.ptau
snarkjs plonk setup square.r1cs pot12_final_2.ptau square_0000.zkey
snarkjs zkey contribute square_0000.zkey square_0001.zkey --name="First contribution" -v -e="some random text"
snarkjs zkey contribute square_0001.zkey square_0002.zkey --name="Second contribution" -v -e="some random text"
snarkjs zkey final square_0002.zkey square.zkey
snarkjs zkey export_verificationkey square.zkey verification_key.json
- 生成证明:
// generate_proof.js
const snarkjs = require("snarkjs");
const fs = require("fs");
async function run() {
const { proof, publicSignals } = await snarkjs.plonk.fullProve(
{ "in": 5 }, // 输入数据
"square_js/square.wasm", // WASM 文件
"square.zkey" // Zkey 文件
);
console.log("Proof: ", proof);
console.log("Public signals: ", publicSignals);
fs.writeFileSync("proof.json", JSON.stringify(proof, null, 2));
fs.writeFileSync("public.json", JSON.stringify(publicSignals, null, 2));
}
run();
- 验证证明:
// verify_proof.js
const snarkjs = require("snarkjs");
const fs = require("fs");
async function run() {
const vKey = JSON.parse(fs.readFileSync("verification_key.json"));
const proof = JSON.parse(fs.readFileSync("proof.json"));
const publicSignals = JSON.parse(fs.readFileSync("public.json"));
const res = await snarkjs.plonk.verify(vKey, publicSignals, proof);
if (res === true) {
console.log("Verification OK");
} else {
console.log("Invalid proof");
}
}
run();
代码解释:
square.circom定义了一个简单的电路,计算输入in的平方。snarkjs用于编译 Circom 电路、生成证明密钥和验证密钥、生成证明和验证证明。generate_proof.js使用输入数据和证明密钥生成证明。verify_proof.js使用验证密钥和公开的输入/输出数据验证证明。
注意: 这个示例只是一个简单的演示,实际应用中可能需要更复杂的电路和更高级的零知识证明技术。
6. 零知识机器学习的未来展望
零知识证明在机器学习领域具有广阔的应用前景,包括:
- 隐私保护的机器学习: 在不暴露数据或模型细节的情况下进行机器学习任务。
- 可信的机器学习: 验证机器学习模型的训练过程和预测结果的完整性。
- 去中心化的机器学习: 在去中心化的环境中进行机器学习任务,例如联邦学习。
- 模型安全: 防止模型被窃取、篡改或滥用。
- 数据安全: 确保用户数据在整个机器学习流程中的安全性。
随着零知识证明技术的不断发展和完善,我们相信它将在未来的机器学习领域发挥越来越重要的作用。
表格:不同 ZKP 技术的比较
| 技术 | 交互性 | 设置阶段 | 证明大小 | 验证时间 | 优点 | 缺点 |
|---|---|---|---|---|---|---|
| Schnorr | 交互式 | 无 | 小 | 快 | 简单易懂,无需可信设置 | 交互式,效率相对较低 |
| NIZK (Fiat-Shamir) | 非交互式 | 无 | 较大 | 较慢 | 非交互式,无需可信设置 | 安全性依赖于哈希函数的安全性 |
| SNARKs | 非交互式 | 可信设置 | 非常小 | 非常快 | 证明大小小,验证速度快 | 需要可信设置,实现复杂 |
| Bulletproofs | 非交互式 | 无 | 较小 | 较慢 | 非交互式,无需可信设置,对数复杂度 | 验证时间相对较长 |
| zk-STARKs | 非交互式 | 无 | 较大 | 较快 | 非交互式,无需可信设置,透明性好 | 证明大小较大 |
结论:保障模型安全与数据隐私的基石
零知识证明作为一种强大的密码学工具,为模型所有权验证和计算完整性验证提供了新的解决方案。虽然实现复杂,但其在保护模型安全和数据隐私方面的潜力巨大,是未来机器学习领域的重要发展方向。通过零知识证明,我们可以构建更安全、可信和隐私保护的机器学习系统。
更多IT精英技术系列讲座,到智猿学院