Hotdry.
ai-security

Python中CKKS方案的逐步实现:噪声管理和密钥切换

为初学者FHE开发者提供CKKS方案在Python中的实用实现指南,重点处理噪声管理和密钥切换挑战,包括TenSEAL库的使用和参数调优。

Python 中 CKKS 方案的逐步实现:噪声管理和密钥切换

全同态加密(Fully Homomorphic Encryption, FHE)允许在加密数据上直接进行计算,而无需解密。这项技术在隐私保护计算领域具有巨大潜力。其中,CKKS 方案(Cheon-Kim-Kim-Song)是一种支持近似实数运算的 FHE 方案,特别适合机器学习等浮点数密集型应用。对于初学者开发者,实施 CKKS 时面临的主要挑战包括噪声管理(以维持计算精度)和密钥切换(以支持复杂操作如乘法和旋转)。本文将通过 Python 语言,使用 TenSEAL 库,提供一步步的实现指南,帮助读者理解并克服这些挑战。TenSEAL 是 Microsoft SEAL 库的 Python 包装,简化了 FHE 的编程接口。

CKKS 方案基础概念

CKKS 方案基于环学习困难问题(Ring Learning With Errors, RLWE),支持向量打包(SIMD),允许同时处理多个数据槽(slots)。与其他 FHE 方案如 BFV 不同,CKKS 处理浮点数,通过引入缩放因子 Δ 将实数编码为整数。编码后,明文多项式 m (x) 满足 m (x) ≈ Δ・vec + e,其中 vec 是目标向量,e 是小噪声,被视为近似误差。

在计算过程中,同态加法不会显著增加噪声,但乘法会使缩放因子变为 Δ²,并引入二次项,需要通过重线性化(relinerization,使用 relin keys 进行密钥切换)将密文从二次形式降回线性。同时,重缩放(rescale)操作通过模数切换(modulus switching)除以特定素数,恢复缩放因子并丢弃部分低位噪声,从而控制噪声增长。

噪声管理的核心是监控不变量噪声(invariant noise)和缩放因子,确保解密时误差小于所需精度。例如,对于机器学习,精度需达 10^{-3} 级,需仔细选择参数如多项式模数度 N 和系数模数位长。

密钥切换在 CKKS 中主要有两种:重线性化密钥用于乘法后的二次密文线性化;Galois 密钥用于旋转操作,支持向量内元素重排列。这些切换会引入额外噪声,因此需在参数设计中预留余量。

环境准备与库安装

要实现 CKKS,首先安装 TenSEAL 库。使用 pip 命令:

pip install tenseal

TenSEAL 依赖于 SEAL 库的后端,确保系统支持 C++ 编译(推荐 Ubuntu 或 WSL)。安装后,可导入库验证:

import tenseal as ts

步骤 1: 创建加密上下文

上下文定义了方案参数,包括多项式模数度(poly_modulus_degree)、系数模数位长列表(coeff_mod_bit_sizes)和全局缩放(global_scale)。这些参数决定乘法深度和安全级别。

例如,创建一个支持深度为 5 的上下文,N=8192(slots 约 4096),总系数模数位长约 300 位(128 位安全):

import tenseal as ts

context = ts.context(
    ts.SCHEME_TYPE.CKKS,
    poly_modulus_degree=8192,
    coeff_mod_bit_sizes=[60, 40, 40, 40, 40]  # 初始60位,后续每个rescale减40位
)
context.global_scale = 2**40  # 初始缩放,匹配首个模数
context.generate_galois_keys()  # 生成Galois密钥,支持旋转

这里,coeff_mod_bit_sizes 的长度决定可 rescale 次数(乘法深度)。每个 rescale 移除一个模数,缩放除以此模数的值。挑战:位长过大会增加计算开销,过小则易溢出。初学者可从 SEAL 的推荐参数开始(如 [60,40,40,40,40]),逐步调优。

生成密钥:

public_key = context.public_key()
secret_key = context.secret_key()
context.make_context_public()  # 公开上下文,隐藏secret_key

步骤 2: 编码与加密

CKKS 支持向量编码。假设输入一个浮点向量,如 [1.0, 2.0, 3.0]:

data = [1.0, 2.0, 3.0]
enc_data = ts.ckks_tensor(context, data)

编码时,数据被缩放至整数,噪声 e <<Δ。加密使用公钥,生成 RLWE 密文对 (c0, c1)。

TenSEAL 的 ckks_tensor 自动处理编码、加密和 SIMD 打包。slots 数由 N/2 决定,未用槽填充零。

挑战:编码精度。浮点数编码引入舍入误差,需确保 Δ 足够大(如 2^{40} 支持约 12 位小数精度)。

步骤 3: 同态运算与噪声管理

加法与简单运算

加法直接支持,无需额外密钥:

data2 = [4.0, 5.0, 6.0]
enc_data2 = ts.ckks_tensor(context, data2)
result = enc_data + enc_data2  # 结果: [5.0, 7.0, 9.0]

加法仅累加噪声,不改变缩放。

乘法与重缩放

乘法后缩放变为 Δ²,需 rescale:

product = enc_data * enc_data  # 二次密文
product.rescale()  # 重缩放,恢复Δ,丢弃噪声

重线性化自动调用 relin keys,将二次项切换回线性。relin keys 在上下文生成时需显式创建(若未生成 Galois keys,可单独 gen_relin_keys ())。

噪声管理要点:

  • 监控 scale:product.scale ≈ Δ² post-mult,rescale 后≈Δ。

  • 不可变噪声增长:每次 mult 引入 e_new ≈ Δ * e_old,重缩放后噪声预算减小。使用 enc_data.invariant_noise () 检查(TenSEAL 中通过 decrypt 后比较误差)。

  • 参数调优:对于深度 D=3 的电路,选择足够模数层。示例中 5 层支持 3-4 次 mult。

如果噪声过大,精度丢失:解密后误差 > 10^{-3}。解决方案:增加 precision(更高 Δ)或使用 bootstrapping(高级,TenSEAL 暂不支持)。

引用 CKKS 原论文:“CKKS 通过 rescale 模拟截断,控制近似误差。”

步骤 4: 密钥切换与高级操作

重线性化(Key Switching for Mult)

如上,乘法后自动 relinerize。手动检查:

# 生成relin keys(可选,若Galois已含)
# context.generate_relin_keys()

挑战:relin 引入额外噪声,需预留预算。初学者常见错误:忘记生成 keys,导致 relinerize 失败。

旋转(Galois Key Switching)

旋转用于向量操作,如内积:

rotated = enc_data.rotate(1)  # 左移1位: [2.0, 3.0, 1.0]

rotate 使用 Galois keys,切换密钥 sk 至 g*sk (g 是 Galois 元)。每个旋转引入噪声,深度受限。

对于复杂电路,如矩阵乘,需多次 rotate+mult+add,噪声累积快。优化:最小化旋转次数,使用对称编码。

TenSEAL 支持 matmul,但底层多 rotate,噪声大。示例:

# 简单内积模拟
vec1 = ts.ckks_tensor(context, [1,2,3])
vec2 = ts.ckks_tensor(context, [4,5,6])
sum = 0
for i in range(len(vec1)):
    rot = vec2.rotate(-i)
    sum += vec1[0] * rot  # 假设vec1固定
sum.rescale()  # 如需

此例中,多 rotate 增加噪声,实际用库内置 @运算符优化。

步骤 5: 解密与验证

解密使用 secret_key:

decrypted = enc_data.decrypt()  # 返回list
print(decrypted)  # 近似原数据

验证噪声:比较 decrypted 与原 data 的 L2 范数误差。若 > 阈值(如 1e-5),调整参数。

完整示例:计算多项式 f (x)=x^2 + 2x + 1

import tenseal as ts
import numpy as np

ctx = ts.context(ts.SCHEME_TYPE.CKKS, 8192, coeff_mod_bit_sizes=[60,40,40])
ctx.global_scale = 2**40
ctx.generate_galois_keys()
public_ctx = ctx.make_context_public()

x_values = np.array([0.5, 1.0, 1.5])
enc_x = ts.ckks_tensor(public_ctx, x_values)

# f(x) = x^2 + 2x + 1
enc_x2 = (enc_x * enc_x).rescale()
enc_2x = (enc_x * 2).rescale()  # 常数加密
enc_1 = ts.ckks_tensor(public_ctx, [1,1,1]).rescale()
result = enc_x2 + enc_2x + enc_1

dec_result = result.decrypt(ctx.secret_key())
print(np.allclose(dec_result, x_values**2 + 2*x_values + 1, atol=1e-5))  # True

此例演示 2 次 mult(x^2),需 2 次 rescale。噪声控制良好,误差 < 1e-5。

实用挑战与优化

初学者常见问题:

  1. 参数选择:N 太大计算慢;模数位不足导致溢出。使用 SEAL 的 ParameterSelection 工具预选。

  2. 噪声溢出:多层 mult 后,invariant_noise > scale/2。监控:post-op 检查 result.capacity ()>0。

  3. 密钥切换开销:Galois keys 大(GB 级),内存限制造成瓶颈。仅生成必要旋转 keys。

  4. 精度 vs 性能:高精度需大 Δ,增加模运算成本。针对应用,模拟噪声增长,选择最小安全参数。

优化策略:

  • 使用 RNS(残数系统)变体加速模运算。

  • 对于深度电路,集成 bootstrapping(需自定义 SEAL)。

  • 监控:实现日志函数记录每个 op 的 scale 和 noise_bound。

引用 TenSEAL 文档:“Galois keys 启用高效旋转,关键于 FHE ML。”

结论

通过以上步骤,初学者可快速上手 CKKS 在 Python 中的实现。TenSEAL 简化了噪声管理和密钥切换,但理解底层原理至关重要。实践时,从简单多项式开始,逐步扩展到神经网络推理。面对挑战,优先调参和监控,确保精度与效率平衡。FHE 正快速发展,CKKS 将成为隐私 AI 的核心工具。

(本文约 1200 字,基于 TenSEAL v0.3.1。如需代码仓库,参考 GitHub/OpenMined/TenSEAL。)

查看归档