文章目录
- 一、batch_encoder (用于 BFV)
- 1. 概述
- 2. 数学原理
- 3. 使用方法
- 4. 代码示例
- 二、ckks_encoder (用于 CKKS)
在 1. bfv_basics.cpp
中,我们展示了如何使用BFV方案执行非常简单的计算。计算是在 plain_modulus
参数的模下执行的,并且 只使用了 BFV 明文多项式的一个系数。这种方法有两个显著的问题:
- 实际应用通常使用整数或实数算术,而不是模算术;
- 我们只使用了明文多项式的一个系数。这真的很浪费,因为明文多项式很大,无论如何都会被完整加密。
对于 (1),人们可能会问为什么不简单地 增加 plain_modulus 参数 直到不发生溢出,并且计算表现得像整数算术一样。问题是增加 plain_modulus 会 增加噪声预算消耗,并且也会 减少初始噪声预算。
在这些示例中,我们将讨论将数据布局到 明文元素 中的其他方法(编码),这些方法允许更多计算而不发生数据类型溢出,并且可以允许 充分利用整个明文多项式。
一、batch_encoder (用于 BFV)
1. 概述
Batch Encoder 的目标是什么?通常,一个同态加密的密文只加密一个明文值。如果我们想对 两个数组 (向量) 逐项相加,就需要为每个元素单独加密成一个密文,然后对每对密文分别进行同态加法,这非常低效。
Batch Encoder 的作用就是将一个明文向量,比如 [a1,a2,...,ak][a_1, a_2, ..., a_k][a1,a2,...,ak] 打包编码到一个 单一的明文多项式 中。然后对这个单一多项式进行加密,得到一个 单一的密文。之后所有同态运算都作用在这一个密文上。一般称之为 SIMD 策略,这极大地提高了同态加密的吞吐量和效率。
设 N 表示 poly_modulus_degree,T 表示 plain_modulus。批处理 允许将 BFV 明文多项式 视为 2×(N/2) 矩阵,每个元素 都是 模 T 的整数。
在 矩阵视图 中,加密操作对加密矩阵 按元素执行,允许用户在完全可向量化的计算中获得几个数量级的加速。
因此,除了最简单的计算外,批处理应该是与 BFV 一起使用的首选方法,当正确使用时,将产生优于任何不使用批处理的实现。
2. 数学原理
核心思想是,如果一个 模多项式 (在 BFV 中即 xN+1x^N+1xN+1) 可以其系数环 (这里是 ZtZ_tZt) 上分解为多个 两两互质的因子,那么原始的、复杂的商环就与这些因子定义的更简单的商环的 直积 (Direct Product) 存在一种被称为 同构 的深刻联系。
- 中国剩余定理:
Batch Encoder 的核心是基于 中国剩余定理 的多项式环分解。基本概念为:- BFV 明文文空间是多项式环 Zt[x]xN+1\frac{Z_t[x]}{x^N+1}xN+1Zt[x]。
- 其中 N=poly_modulus_degreeN=poly\_modulus\_degreeN=poly_modulus_degree,t=plain_modulust=plain\_modulust=plain_modulus。
- 当 t 是 满足特定条件的素数 时,这个环可以分解。这个条件就是 t≡1(mod2N)t \equiv 1\ \ (mod\ 2N)t≡1 (mod 2N)当满足这个条件时,多项式 xN+1x^N+1xN+1 可以完全分解为 N 个不同的线性因子。
- 环的分解:
当上面的条件满足的时候,我们有:Zt[x]xN+1≅Zt×Zt×...×Zt(N个副本)\frac{Z_t[x]}{x^N+1} \cong Z_t \times Z_t \times ... \times Z_t\ \ \ (N 个副本)xN+1Zt[x]≅Zt×Zt×...×Zt (N个副本)≅\cong≅ 是同构的意思,表示两个代数结构是同构的,即它们在结构上是相同的,尽管它们的元素可能不同。
这意味着:- 一个度数小于 N 的多项式 <—> N 个独立的模 t 整数。
- 多项式运算 <—> 对应位置的元素运算。
- 实现了 SIMD 计算。
- 矩阵视图:
在 SEAL 中,N 个槽被组织为 2×(N/2)2 \times (N / 2)2×(N/2) 矩阵:
[ slot_0, slot_1, ..., slot_(N/2-1) ]
[ slot_(N/2), ..., slot_(N-1) ]
3. 使用方法
- 第一步当然是设置 加密参数,其中加密方案、多项式模数次数、密文系数模数何止前的思路一样。但是要求将
plain_modulus
设置为一个 与1
模2*poly_modulus_degree
同余的素数。这一步 Microsoft SEAL 提供了一个辅助方法来寻找这样的素数。在这个示例中,我们创建一个支持批处理的 20 位素数。
parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20)); // 设置支持批处理的明文模数
- 用设置好的参数生成上下文;
- 生成
KeyGenerator
、Encryptor
、Evaluator
、Decryptor
; - 生成
BatchEncoder
的实例,批处理 '槽’的总数 等于poly_modulus_degree = N
,这些槽被组织成2×(N/2)
矩阵,可以进行加密和计算。每个槽 包含一个 模plain_modulus
的整数; - 先将数据存到
vector<uint64_t> pod_matrix(slot_count, 0ULL)
向量中。然后通过BatchEncoder类
提供的方法.encode
,将矩阵编码为明文Plaintext类
的实例中; - 解码的过程完全相反,通过
BatchEncoder类
提供的.decode
方法,将Plaintext类
明文解码为vector<uint64_t>
向量。 - 对明文进行加密和运算的方法同之前一摸一样。
4. 代码示例
print_example_banner("Example: Encoders / Batch Encoder");EncryptionParameters parms(scheme_type::bfv); // 创建BFV方案的加密参数对象size_t poly_modulus_degree = 8192; // 设置多项式模数次数为8192parms.set_poly_modulus_degree(poly_modulus_degree); // 应用多项式模数次数设置parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree)); // 设置系数模数为BFV默认值parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20)); // 设置支持批处理的明文模数SEALContext context(parms); // 使用参数创建SEAL上下文对象KeyGenerator keygen(context); // 创建密钥生成器对象SecretKey secret_key = keygen.secret_key(); // 生成私钥PublicKey public_key; // 声明公钥对象keygen.create_public_key(public_key); // 创建公钥RelinKeys relin_keys; // 声明重线性化密钥对象keygen.create_relin_keys(relin_keys); // 创建重线性化密钥Encryptor encryptor(context, public_key); // 创建加密器,使用公钥Evaluator evaluator(context); // 创建计算器Decryptor decryptor(context, secret_key); // 创建解密器,使用私钥BatchEncoder batch_encoder(context); // 创建批处理编码器对象size_t slot_count = batch_encoder.slot_count(); // 获取槽的总数size_t row_size = slot_count / 2; // 计算每行的大小cout << "明文矩阵行大小: " << row_size << endl;vector<uint64_t> pod_matrix(slot_count, 0ULL); // 创建大小为slot_count的向量,初始化为0pod_matrix[0] = 0ULL; // 设置第一行第一个元素pod_matrix[1] = 1ULL; // 设置第一行第二个元素pod_matrix[2] = 2ULL; // 设置第一行第三个元素pod_matrix[3] = 3ULL; // 设置第一行第四个元素pod_matrix[row_size] = 4ULL; // 设置第二行第一个元素pod_matrix[row_size + 1] = 5ULL; // 设置第二行第二个元素pod_matrix[row_size + 2] = 6ULL; // 设置第二行第三个元素pod_matrix[row_size + 3] = 7ULL; // 设置第二行第四个元素Plaintext plain_matrix; // 声明明文对象batch_encoder.encode(pod_matrix, plain_matrix); // 将矩阵编码为明文vector<uint64_t> pod_result; // 声明结果向量batch_encoder.decode(plain_matrix, pod_result); // 解码明文到向量print_matrix(pod_result, row_size); // 打印结果矩阵Ciphertext encrypted_matrix; // 声明密文对象encryptor.encrypt(plain_matrix, encrypted_matrix); // 加密明文/*对密文的操作会导致在所有8192个槽(矩阵元素)中同时执行同态操作。为了说明这一点,我们构造另一个明文矩阵[ 1, 2, 1, 2, 1, 2, ..., 2 ][ 1, 2, 1, 2, 1, 2, ..., 2 ]并将其编码为明文。*/vector<uint64_t> pod_matrix2; // 声明第二个矩阵向量for (size_t i = 0; i < slot_count; i++) // 循环填充向量pod_matrix2.push_back((i & size_t(0x1)) + 1); // 使用位运算生成1,2交替的模式Plaintext plain_matrix2; // 声明第二个明文对象batch_encoder.encode(pod_matrix2, plain_matrix2); // 编码第二个矩阵print_matrix(pod_matrix2, row_size); // 第二个输入明文矩阵evaluator.add_plain_inplace(encrypted_matrix, plain_matrix2); // 将密文与明文相加(就地操作)evaluator.square_inplace(encrypted_matrix); // 对密文进行平方(就地操作)evaluator.relinearize_inplace(encrypted_matrix, relin_keys); // 重线性化以减少密文大小/*我们还剩多少噪声预算?*/cout << " + 结果中的噪声预算: " << decryptor.invariant_noise_budget(encrypted_matrix) << " 位" << endl;/*我们解密并分解明文以恢复矩阵形式的结果。*/Plaintext plain_result; // 声明结果明文对象print_line(__LINE__);cout << "解密并解码结果。" << endl;decryptor.decrypt(encrypted_matrix, plain_result); // 解密密文batch_encoder.decode(plain_result, pod_result); // 解码明文到向量cout << " + 结果明文矩阵 ...... 正确。" << endl;print_matrix(pod_result, row_size);
二、ckks_encoder (用于 CKKS)
暂时掠过