
SEAL (Simple Encrypted Arithmetic Library)是微软开源的一个同态加密软件库。简单来说它是一套工具让开发者可以在不解密数据的情况下直接对加密数据进行分析和计算。示例 1: BFV 基础(1_bfv_basics.cpp) - 加密整数运算https://github.com/microsoft/SEAL/blob/main/native/examples/1_bfv_basics.cppBFV是一种主流的全同态加密方案其核心特点是在加密数据上支持精确的整数运算加密计算的全过程流程图参数设置配置了多项式次数、系数模数、明文模数。密钥与工具生成创建了公私钥、加密器、计算器、解密器。朴素计算路径计算 x²1 和 (x1)²观察到密文膨胀、噪声消耗。执行最终乘法观察到噪声预算归零。解密失败得到错误结果。重线性化优化路径生成重线性化密钥。每做一次乘法立刻用重线性化把密文大小从3压回2。用压缩后的标准尺寸密文执行最终乘法。噪声预算充足解密成功得到正确结果0x54。无效参数演示故意改错参数展示如何查看错误信息。这种方法存在两个显著问题实际应用通常使用整数或实数运算而非模算术运算我们只使用了明文多项式中的一个系数。这非常浪费因为明文多项式本身很大而且无论如何都会被完整加密。注噪声预算”是衡量“故意引入的随机数噪声”还能增加多少而不导致解密失败的一个安全指标。每次同态运算尤其是乘法都会让噪声增大。加法的噪声增长是线性的而乘法的噪声增长是乘性的会让噪声急剧膨胀。当总噪声大到超过一个绝对阈值由加密参数决定时它就彻底淹没了原始数据。此时噪声预算就归零了。这时再去解密解密算法会试图移除一层固定的噪声但因为总噪声已经过大移除后剩下的就是一个被严重破坏的、随机的错误结果。结论噪声预算归零意味着噪声已经大到必然导致解密失败。示例 2: 编码器(2_encoders.cpp) - 编码复杂数据https://github.com/microsoft/SEAL/blob/main/native/examples/2_encoders.cppBatchEncoder(BFV方案批处理)参数设置配置了多项式次数、系数模数并使用PlainModulus::Batching生成一个特殊的素数作为明文模数以开启批处理功能。数据准备与编码将待处理数据视为一个2 × (N/2)矩阵展平为向量用BatchEncoder将其打包编码为一个明文多项式。同态运算对密文进行加、乘等操作效果会自动作用在矩阵的每一个元素上实现单指令多数据流并行。解码与验证解密后用BatchEncoder将明文多项式解码回向量验证所有元素的计算结果。BatchEncoder批处理器它的作用是把一个包含几千个数字的向量数组打包编码成一个能放进密文箱子的明文多项式。反过来它也能把解密后的明文多项式解码还原成那个向量。它就像一个数据打包和拆包的流水线机器。CKKSEncoder(CKKS方案实数编码)参数设置配置了多项式次数、系数模数但没有明文模数因为它处理的是近似的小数而不是对某个模数取余的整数。这是CKKS与BFV的根本区别。数据准备与编码准备一个浮点数向量并设定一个关键参数scale缩放因子如2^30。CKKSEncoder会将所有小数乘以scale放大为整数再编码为明文。同态运算与Scale变化对密文进行乘法时其内部的scale值会随之倍增如平方后从2^30变成2^60。这是CKKS方案需要管理的核心特性。解码与验证解密后CKKSEncoder利用当前的scale值将整数缩小解码得到近似的实数结果。示例 3: 级别(3_levels.cpp) - 理解密文级别https://github.com/microsoft/SEAL/blob/main/native/examples/3_levels.cpp1. 概念建立通过手动指定coeff_modulus为一串素数{50, 30, 30, 50, 50}创造了“模数切换链”这个楼层结构。2. 结构可视化遍历并打印了整个链条看到了密钥在顶层数据从下一层开始。3. 规则验证通过打印密钥对象id和密文对象id与层级id验证了密钥对象和密文对象各自身处不同的固定楼层。4. 核心操作演示了mod_switch_to_next如何把密文逐层下搬并观察到随意切换会损失大量噪声预算但是解密后发现结果正确这说明模数切换本身没有破坏数据。5. 正确用法在计算阶段之间进行模数切换切换后的噪声预算和切换前一样同时为后续计算“瘦身”提速。6. 可选配置SEALContext(parms, false)通过false参数禁用长链SEAL只创建必不可少的最少层级密钥层 (Key Level)和最高数据层 (First Data Level)以适应不需要频繁切换的场景。注模数切换是一种将密文参数沿链向下改变的技术在完成一个计算阶段、噪声预算消耗自然结束时再进行切换就不会额外损失预算在完成一个计算阶段后噪声预算通常就是接近被消耗到上限的时候。那么在无损失的前提下降低密文级别可以缩减密文大小提升后续运算速度。这是一种“瘦身”优化。同态运算尤其是乘法的复杂度和密文多项式系数的位数直接相关。位数越小计算越快。切换到底层后系数的模数变小了所以后续的乘法等操作就会更快。示例 4: BGV 基础(4_bgv_basics.cpp)https://github.com/microsoft/SEAL/blob/main/native/examples/4_bgv_basics.cpp1. 参数设置配置了多项式次数8192、系数模数默认自动推荐并设置了明文模数以开启批处理。最关键的是方案类型指定为bgv。2. 密钥与工具生成创建了公私钥、加密器、计算器、解密器以及重线性化密钥。与BFV流程完全一致。3. 数据编码与加密使用BatchEncoder将整数矩阵如 [1, 2, 3, 4]打包编码为明文然后加密成密文。流程与BFV的批处理完全一致。4. BGV核心计算循环对于每一次乘法如计算平方严格执行一个固定的三步操作组合乘法执行密文乘法如平方密文大小膨胀。重线性化立即压缩密文大小从3降回2。模数切换紧接着执行mod_switch_to_next将密文降至更低的模数级别。这是BGV控制噪声、保证深度计算可行的关键步骤是必需操作而非可选优化。4. 解密与解码计算完成后解密获得明文多项式再使用BatchEncoder解码恢复出最终的结果矩阵。计算结果正确没有使用模数切换噪声预算在x^8后noise budget0得到结果是错误的。结果输出noise budget对比注通过每次乘法后强制进行模数切换主动、持续地重置噪声增长率虽然让单次噪声预算看起来更少但换来了完成更深度计算的能力。要在实际应用中达到最优的噪声预算消耗速率需要仔细选择插入模数切换的位置并手动挑选 coeff_modulus。示例 5: CKKS 基础(5_ckks_basics.cpp) - 加密实数运算最常用https://github.com/microsoft/SEAL/blob/main/native/examples/5_ckks_basics.cpp1. 参数设置搭配coeff_modulus的素数序列{ 60, 40, 40, 60 }和初始scale2^40为重缩放奠定基础。2. 标准运算循环对于每次乘法执行乘法 - 重线性化 - 重缩放模数切换以稳定缩放因子。每次乘法会导致scale扩大需重缩放降低scale而模数切换会使密文级别下降并且每次重缩放除以的素数是“接近”而不是“等于”2^40所以scale精确值有微小差异。3. 解决对齐难题在混合不同“深度”的计算结果如多项式求和时必须通过手动设置缩放因子和普通模数切换强行统一缩放因子为理论目标值 2^40统一所有项的楼层模数切换拉到最低楼层才能正确相加。4. 验证结果代码先用明文计算预期真实值解密、解码密文并对比结果结果高度近似。结论CKKS 给出的是高精度的近似结果而非像 BFV/BGV 那样的精确整数。示例 6: 旋转(6_rotation.cpp) - 加密向量循环旋转https://github.com/microsoft/SEAL/blob/main/native/examples/6_rotation.cppBFV 旋转总结1. 操作函数BFV使用evaluator.rotate_rows_inplace()进行行的循环左/右移使用evaluator.rotate_columns_inplace()进行列旋转即两行互换。2. 通用性BFV的旋转需要GaloisKeys伽罗瓦密钥并且也基本不消耗噪声预算。3. 数据结构BFV的批处理将数据视为二维矩阵。因此它的旋转操作是针对矩阵的行或列进行。CKKS 旋转总结1. 操作函数CKKS使用evaluator.rotate_vector()来旋转一维向量。注意函数名是rotate_vector()不是BFV里用的rotate_rows2. 通用性和BFV的旋转一样CKKS的旋转同样需要GaloisKeys并且也不消耗噪声预算。3. 数据结构CKKS操作的是一维向量而BFV批处理操作的是二维矩阵的行或列。这是两者在应用旋转时的关键区别。示例 7: 序列化(7_serialization.cpp) - 保存和加载对象https://github.com/microsoft/SEAL/blob/main/native/examples/7_serialization.cpp解决的核心问题如何高效地在不同角色间如客户端与服务器传输同态加密的各种对象并最大限度地压缩数据体积从而构建实用的外包计算通信协议。1. 服务器设置并共享参数创建CKKS参数然后使用parms.save(parms_stream)将参数对象序列化到一个共享的C字符串流模拟网络压缩模式zlib/zstd可以显著减小数据体积2. 客户端生成密钥并加密数据从流中加载参数生成秘密密钥并使用sk.save(sk_stream)保存。密钥体积代码创建了两个重线性化密钥一个是“种子”态的SerializableRelinKeys一个是完全展开的RelinKeys。密文体积代码用两种模式加密了两个数2.3 和 4.5标准的encrypt公钥模式密文不可种子化和encrypt_symmetric秘密密钥模式密文可种子化。观察打印出的密文大小后者会小得多。这说明了为什么在不需要公钥分发时秘密密钥模式是更好的选择。3. 服务器计算并传回结果从流中加载重线性化密钥和两个密文执行乘法、重线性化和重缩放。将计算结果密文保存到数据流。注意这个结果密文已经过计算无法再被保存为“种子”状态。4. 客户端解密验证从流中加载秘密密钥和结果密文解密、解码验证结果是否接近2.3 * 4.5 10.35