1.BatchEncoder
设N为poly_modulus_degree,T为plain_modulus。批处理将BFV明文多项式视为2×(N/2)矩阵,每个元素都是一个模T整数。在矩阵视图中,加密操作在加密矩阵上逐个执行element-wise。
1 2 3 4
| EncryptionParameters parms(scheme_type::bfv); size_t poly_modulus_degree = 8192; parms.set_poly_modulus_degree(poly_modulus_degree); parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
|
为了启用批处理,我们需要将plain_modulus设置为一个素数,它全等于1模2*oly_modulus_degree,SEAL提供了一种辅助方法来查找这样一个素数。在本例中我们创建了一个支持批处理的20位素数。
1 2 3 4 5
| parms.set_plain_modulus(PlainModulus::Batching(poly_modulus_degree, 20));
SEALContext context(parms); print_parameters(context); cout << endl;
|
可以通过查看由SEALContext创建的加密参数限定符来验证是否启用了批处理
1 2
| auto qualifiers = context.first_context_data()->qualifiers(); cout << "Batching enabled: " << boolalpha << qualifiers.using_batching << endl;
|
生成密钥对、加密/解密/评估器 1 2 3 4 5 6 7 8 9
| 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);
|
生成批处理编码类实例 1
| BatchEncoder batch_encoder(context);
|
批处理slot的总数等于poly_modulus_degree,N,这些slot被组织为2×(N/2)矩阵,可以对其进行加密和计算。每个slot都包含一个整数模plain_modulus。
1 2 3
| size_t slot_count = batch_encoder.slot_count(); size_t row_size = slot_count / 2; cout << "Plaintext matrix row size: " << row_size << endl;
|
矩阵明文被简单地作为一个扁平的数字向量提供给BatchEncoder。'row_size'最多的组成第一行,其余组成第二行。我们创建如下矩阵:
1 2
| [ 0, 1, 2, 3, 0, 0, ..., 0 ] [ 4, 5, 6, 7, 0, 0, ..., 0 ]
|
1 2 3 4 5 6 7 8 9 10 11 12
| vector<uint64_t> pod_matrix(slot_count, 0ULL); pod_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;
cout << "Input plaintext matrix:" << endl; print_matrix(pod_matrix, row_size);
|
首先使用BatchEncoder将矩阵编码为明文多项式
1 2 3 4
| Plaintext plain_matrix; print_line(__LINE__); cout << "Encode plaintext matrix:" << endl; batch_encoder.encode(pod_matrix, plain_matrix);
|
解码,验证编码正确性
1 2 3 4 5
| vector<uint64_t> pod_result; cout << " + Decode plaintext matrix ...... Correct." << endl; batch_encoder.decode(plain_matrix, pod_result); print_matrix(pod_result, row_size);
|
加密编码明文
1 2 3 4 5 6
| Ciphertext encrypted_matrix; print_line(__LINE__); cout << "Encrypt plain_matrix to encrypted_matrix." << endl; encryptor.encrypt(plain_matrix, encrypted_matrix); cout << " + Noise budget in encrypted_matrix: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;
|
对密文的操作是在所有8192个slot中同时执行的同态操作。为了说明这一点,构造以下明文矩阵:
1 2
| [ 1, 2, 1, 2, 1, 2, ..., 2 ] [ 1, 2, 1, 2, 1, 2, ..., 2 ]
|
将其编码为明文
1 2 3 4 5 6 7 8 9 10
| vector<uint64_t> pod_matrix2; for (size_t i = 0; i < slot_count; i++) { pod_matrix2.push_back((i & size_t(0x1)) + 1); } Plaintext plain_matrix2; batch_encoder.encode(pod_matrix2, plain_matrix2); cout << endl; cout << "Second input plaintext matrix:" << endl; print_matrix(pod_matrix2, row_size);
|
将第二个明文矩阵与加密矩阵相加,并对和执行平方操作
1 2 3 4 5
| print_line(__LINE__); cout << "Sum, square, and relinearize." << endl; evaluator.add_plain_inplace(encrypted_matrix, plain_matrix2); evaluator.square_inplace(encrypted_matrix); evaluator.relinearize_inplace(encrypted_matrix, relin_keys);
|
查看剩余噪声预算
1
| cout << " + Noise budget in result: " << decryptor.invariant_noise_budget(encrypted_matrix) << " bits" << endl;
|
解密并解码以恢复明文结果(矩阵)
1 2 3 4 5 6 7
| Plaintext plain_result; print_line(__LINE__); cout << "Decrypt and decode result." << endl; decryptor.decrypt(encrypted_matrix, plain_result); batch_encoder.decode(plain_result, pod_result); cout << " + Result plaintext matrix ...... Correct." << endl; print_matrix(pod_result, row_size);
|
当所需的加密计算高度可并行化时,批处理允许我们有效地使用全明文多项式。但是,他并没有解决数据类型溢出问题(除非明文模数非常大)。CKKSEncoder解决了该问题,但代价是只能得到近似的结果。
2.CKKSEncoder
首先为CKKS方案创建加密参数,与BFV方案相比,有两个重要的区别:
(1)CKKS不使用plain_modulus参数
(2)在使用CKKS方案时,以特定的方式选择coeff_modulus是非常重要的
在本例中,使用CoeffModulus::Create生成5个40位素数。
1 2 3 4 5
| EncryptionParameters parms(scheme_type::ckks);
size_t poly_modulus_degree = 8192; parms.set_poly_modulus_degree(poly_modulus_degree); parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, { 40, 40, 40, 40, 40 }));
|
创建SEALContext实例
1 2 3
| SEALContext context(parms) print_parameters(context) cout << endl
|
创建密钥对、加密/解密/评估器
1 2 3 4 5 6 7 8 9 10
| KeyGenerator keygen(context); auto 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);
|
要创建CKKS明文,需要一个特殊的编码器。CKKSEncoder将实数或复数的向量编码成明文对象。
1
| CKKSEncoder encoder(context)
|
在CKKS中,slot的数量是poly_modulus_degree/2,每个slot编码一个实数或复数(而在BatchEncoder中slot的数量等于poly_modulus_degree)
1 2
| size_t slot_count = encoder.slot_count(); cout << "Number of slots: " << slot_count << endl;
|
创建一个小向量进行编码,在编码时,CKKSEncoder将隐式地用0填充
1 2 3
| vector<double> input{ 0.0, 1.1, 2.2, 3.3 }; cout << "Input vector: " << endl; print_vector(input);
|
使用CKKSEncoder编码,输入的浮点系数将由参数scale放大。这是必须的,因为即使是在CKKS方案中,明文元素基本上也是整系数多项式。scale决定了编码的位精度,这影响了结果的准确性。在CKKS中,消息模coeff_modulus后存储(在BFV中,消息模plain_modulu存储),因此缩放后的消息不能太接近coeff_modulu。在这种情况下,coeff_modulus相当大(218位)。
1 2 3 4 5 6
| Plaintext plain; double scale = pow(2.0, 30); print_line(__LINE__); cout << "Encode input vector." << endl; encoder.encode(input, scale, plain);
|
解码,检验编码正确性
1 2 3 4
| vector<double> output; cout << " + Decode input vector ...... Correct." << endl; encoder.decode(plain, output); print_vector(output);
|
加密(与BFV类似)
1 2 3 4
| Ciphertext encrypted; print_line(__LINE__); cout << "Encrypt input vector, square, and relinearize." << endl; encryptor.encrypt(plain, encrypted);
|
对密文的基本操作仍然是容易的。本例中我们对密文进行了平方、解密、解码操作,并打印了结果。注意,由于隐式补零,encoding返回一个full size(poly_modulus_degree/2)向量。结果中的scale变为原始scale的平方。
1 2 3 4 5 6 7 8 9 10 11 12 13
| evaluator.square_inplace(encrypted); evaluator.relinearize_inplace(encrypted, relin_keys);
cout << " + Scale in squared input: " << encrypted.scale() << " (" << log2(encrypted.scale()) << " bits)" << endl;
print_line(__LINE__); cout << "Decrypt and decode." << endl; decryptor.decrypt(encrypted, plain); encoder.decode(plain, output); cout << " + Result vector ...... Correct." << endl; print_vector(output);
|
CKKS方案允许在加密计算之间减少scale。