去年学习过seal同态加密的相关代码了,但是今年用的时候发现忘没了。因此写个博客记录一下,以后如果有需要就回来看看自己写的。
1、参数设置
/*
首先需要设置EncryptionParameters类的实例。理解不同参数的行为、它们如何
影响加密方案、性能和安全级别是非常关键的。需要设置三个加密参数:
poly_modulus_degree(多项式模数的度数)
coeff_modulus([密文]系数模数)
plain_modulus(明文模数;仅用于BFV方案)
*/
EncryptionParameters parms(scheme_type::bfv);
/*
我们设置的第一个参数是“多项式模数”的度数。它必须是2的正整数次幂,表示2的
幂次循环多项式的度数;不需要理解这句话的具体含义。
较大的poly_modulus_degree会使密文的大小变大,并且所有操作速度变慢,但可以支
持更复杂的加密计算。推荐的值为1024、2048、4096、8192、16384、32768,但也可以
超出这个范围。
*/
size_t poly_modulus_degree = 4096;
parms.set_poly_modulus_degree(poly_modulus_degree);
/*
接下来,我们设置了密文的“系数模数”(coeff_modulus)。该参数是一个大整数,
是不同素数的乘积,每个素数的大小不超过60位。它以这些素数的向量形式表示,每个素
数由Modulus类的实例表示。coeff_modulus的位长度是其素数因子的位长度之和。
较大的coeff_modulus意味着较大的噪声预算,因此可以支持更多的加密计算能力。然而,
coeff_modulus的总位长度的上限由poly_modulus_degree决定,如下所示:
+----------------------------------------------------+
| poly_modulus_degree | 最大 coeff_modulus 位长度 |
+---------------------+------------------------------+
| 1024 | 27 |
| 2048 | 54 |
| 4096 | 109 |
| 8192 | 218 |
| 16384 | 438 |
| 32768 | 881 |
+---------------------+------------------------------+
这些数字还可以在native/src/seal/util/hestdparms.h中的SEAL_HE_STD_PARMS_128_TC
函数中找到,并且还可以从函数CoeffModulus::MaxBitCount(poly_modulus_degree)中
获取。
例如,如果poly_modulus_degree为4096,那么coeff_modulus可以由三个36位素数
(共108位)组成。
Microsoft SEAL提供了用于选择coeff_modulus的辅助函数。对于新用户,最简单的方法是使
用CoeffModulus::BFVDefault(poly_modulus_degree),它返回一个std::vector<Modulus>,
其中包含了对于给定poly_modulus_degree的一个通常很好的选择。
*/
parms.set_coeff_modulus(CoeffModulus::BFVDefault(poly_modulus_degree));
/*
明文模数可以是任何正整数,尽管在这里我们将其设置为2的幂次。明文模数决定了明文数据
类型的大小以及乘法中噪声预算的消耗。因此,为了获得最佳性能,尽量将明文数据类型保持得越
小越好。在刚加密的密文中的噪声预算为:
log2(coeff_modulus/plain_modulus) (bits)
在同态乘法中,噪声预算的消耗形式为log2(plain_modulus) +(其他项)。
明文模数是BFV方案特有的,并且在使用CKKS方案时不能设置。
*/
parms.set_plain_modulus(1024);
2、创建密钥、加密、评估、解密器
/*
现在,所有的参数都已设置好,我们可以构建一个SEALContext对象。
这是一个重要的类,用于检查我们刚刚设置的参数的有效性和属性。
*/
SEALContext context(parms);
/*
现在,我们准备生成密钥对(公钥和私钥)。为此,我们需要KeyGenerator
类的一个实例。构造一个KeyGenerator会自动生成一个私钥。然后,我们可以使用KeyGenerator::create_public_key创建任意数量的公钥。
*/
KeyGenerator keygen(context);
SecretKey secret_key = keygen.secret_key();
PublicKey public_key;
keygen.create_public_key(public_key);
/*
为了能够进行加密,我们需要构建一个Encryptor的实例。请注意,Encryptor只
需要公钥作为参数。
*/
Encryptor encryptor(context, public_key);
/*
对密文的计算是通过Evaluator类进行的。在真实的使用情况下,Evaluator不会由持有密钥的同一方构建。
*/
Evaluator evaluator(context);
/*
Decryptor的实例来验证一切是否正常工作。
*/
Decryptor decryptor(context, secret_key);
3、样例操作
/*
我们的示例中要计算:4x^4 + 8x^3 + 8x^2 + 8x + 4
首先,我们创建一个包含常数6的明文。对于明文元素,我们使用一个以十六进制表示系数
的字符串作为参数的构造函数来创建它。
*/
uint64_t x = 6;
Plaintext x_plain(uint64_to_hex_string(x));
cout << "Express x = " + to_string(x) + " as a plaintext polynomial 0x" + x_plain.to_string() + "." << endl;
/*
然后,我们对明文进行加密,得到一个密文。
*/
Ciphertext x_encrypted;
cout << "Encrypt x_plain to x_encrypted." << endl;
encryptor.encrypt(x_plain, x_encrypted);
/*
在Microsoft SEAL中,一个有效的密文由两个或多个多项式组成,其系数是在coeff_modulus中
所有素数的乘积下的整数。密文中多项式的数量被称为其“大小”,可以通过Ciphertext::size()
方法获取。一个新加密的密文始终具有大小为2。
*/
cout << " + size of freshly encrypted x: " << x_encrypted.size() << endl;
/*
噪声预算计算如下
*/
cout << " + noise budget in freshly encrypted x: " << decryptor.invariant_noise_budget(x_encrypted) << " bits"<< endl;
/*
在使用Microsoft SEAL时,通常有利于以最小化最长的连续乘法链的方式进行计算。换句话说,最好以最小化计算的乘法深度的方式评估加密的计算,因为总的噪声预算消耗与乘法深度成正比。例如,在我们的示例计算中,将多项式因式分解为:4x^4 + 8x^3 + 8x^2 + 8x + 4 = 4(x + 1)^2 * (x^2 + 1)
可以得到一个简单的深度为2的表示。因此,我们先分别计算(x + 1)^2和(x^2 + 1),然后将它们相乘,并乘以4。
首先,我们计算x^2并添加一个明文"1"。从打印输出中可以清楚地看到乘法消耗了大量的噪声预算。
*/
cout << "Compute x_sq_plus_one (x^2+1)." << endl;
Ciphertext x_sq_plus_one;
evaluator.square(x_encrypted, x_sq_plus_one);
Plaintext plain_one("1");
evaluator.add_plain_inplace(x_sq_plus_one, plain_one);
/*
加密的乘法会导致输出密文的大小增加。更准确地说,如果输入密文的大小为M和N,则经过同态乘法后的输出密文的大小将为M+N-1。在这种情况下,我们进行平方操作,观察到大小的增长和噪声预算的消耗。
*/
cout << " + size of x_sq_plus_one: " << x_sq_plus_one.size() << endl;
cout << " + noise budget in x_sq_plus_one: " << decryptor.invariant_noise_budget(x_sq_plus_one) << " bits"
<< endl;
/*
尽管密文的大小增加了,只要噪声预算还没有降到0,解密仍然可以正常工作。
*/
Plaintext decrypted_result;
cout << " + decryption of x_sq_plus_one: ";
decryptor.decrypt(x_sq_plus_one, decrypted_result);
cout << "0x" << decrypted_result.to_string() << " ...... Correct." << endl;
/*
计算(x+1)^2
*/
cout << "Compute x_plus_one_sq ((x+1)^2)." << endl;
Ciphertext x_plus_one_sq;
evaluator.add_plain(x_encrypted, plain_one, x_plus_one_sq);
evaluator.square_inplace(x_plus_one_sq);
cout << " + size of x_plus_one_sq: " << x_plus_one_sq.size() << endl;
cout << " + noise budget in x_plus_one_sq: " << decryptor.invariant_noise_budget(x_plus_one_sq) << " bits"
<< endl;
cout << " + decryption of x_plus_one_sq: ";
decryptor.decrypt(x_plus_one_sq, decrypted_result);
cout << "0x" << decrypted_result.to_string() << " ...... Correct." << endl;
/*
最后计算:(x^2 + 1) * (x + 1)^2 * 4.
*/
cout << "Compute encrypted_result (4(x^2+1)(x+1)^2)." << endl;
Ciphertext encrypted_result;
Plaintext plain_four("4");
evaluator.multiply_plain_inplace(x_sq_plus_one, plain_four);
evaluator.multiply(x_sq_plus_one, x_plus_one_sq, encrypted_result);
cout << " + size of encrypted_result: " << encrypted_result.size() << endl;
cout << " + noise budget in encrypted_result: " << decryptor.invariant_noise_budget(encrypted_result) << " bits"
<< endl;
cout << "NOTE: Decryption can be incorrect if noise budget is zero." << endl;
cout << endl;
cout << "~~~~~~ A better way to calculate 4(x^2+1)(x+1)^2. ~~~~~~" << endl;
/*
噪声预算已经降到了0,这意味着无法期望解密能给出正确的结果。这是因为由于先前的平方操作,x_sq_plus_one和x_plus_one_sq这两个密文都由3个多项式组成,而对大密文的同态操作比对小密文的计算消耗更多的噪声预算。在较小的密文上进行计算也具有显著的计算成本优势。
"重新线性化"是一种操作,它将乘法后的密文大小减小到初始大小2。因此,在下一次乘法之前对一个或两个输入密文进行重新线性化可以对噪声增长和性能产生巨大的正面影响,尽管重新线性化本身具有显著的计算成本。只有将大小为3的密文重新线性化为大小为2,所以通常用户会希望在每次乘法之后进行重新线性化,以保持密文的大小为2。
重新线性化需要特殊的"重新线性化密钥",可以将其视为一种公钥。可以使用KeyGenerator轻松地创建重新线性化密钥。
在BFV和CKKS方案中,重新线性化的使用方式类似,但在本示例中我们继续使用BFV。我们重复之前的计算,但这次在每次乘法后进行重新线性化
*/
cout << "Generate relinearization keys." << endl;
RelinKeys relin_keys;
keygen.create_relin_keys(relin_keys);
cout << "Compute and relinearize x_squared (x^2)," << endl;
cout << string(13, ' ') << "then compute x_sq_plus_one (x^2+1)" << endl;
Ciphertext x_squared;
evaluator.square(x_encrypted, x_squared);
cout << " + size of x_squared: " << x_squared.size() << endl;
evaluator.relinearize_inplace(x_squared, relin_keys);
cout << " + size of x_squared (after relinearization): " << x_squared.size() << endl;
evaluator.add_plain(x_squared, plain_one, x_sq_plus_one);
cout << " + noise budget in x_sq_plus_one: " << decryptor.invariant_noise_budget(x_sq_plus_one) << " bits"
<< endl;
cout << " + decryption of x_sq_plus_one: ";
decryptor.decrypt(x_sq_plus_one, decrypted_result);
cout << "0x" << decrypted_result.to_string() << " ...... Correct." << endl;
print_line(__LINE__);
Ciphertext x_plus_one;
cout << "Compute x_plus_one (x+1)," << endl;
cout << string(13, ' ') << "then compute and relinearize x_plus_one_sq ((x+1)^2)." << endl;
evaluator.add_plain(x_encrypted, plain_one, x_plus_one);
evaluator.square(x_plus_one, x_plus_one_sq);
cout << " + size of x_plus_one_sq: " << x_plus_one_sq.size() << endl;
evaluator.relinearize_inplace(x_plus_one_sq, relin_keys);
cout << " + noise budget in x_plus_one_sq: " << decryptor.invariant_noise_budget(x_plus_one_sq) << " bits"
<< endl;
cout << " + decryption of x_plus_one_sq: ";
decryptor.decrypt(x_plus_one_sq, decrypted_result);
cout << "0x" << decrypted_result.to_string() << " ...... Correct." << endl;
print_line(__LINE__);
cout << "Compute and relinearize encrypted_result (4(x^2+1)(x+1)^2)." << endl;
evaluator.multiply_plain_inplace(x_sq_plus_one, plain_four);
evaluator.multiply(x_sq_plus_one, x_plus_one_sq, encrypted_result);
cout << " + size of encrypted_result: " << encrypted_result.size() << endl;
evaluator.relinearize_inplace(encrypted_result, relin_keys);
cout << " + size of encrypted_result (after relinearization): " << encrypted_result.size() << endl;
cout << " + noise budget in encrypted_result: " << decryptor.invariant_noise_budget(encrypted_result) << " bits"
<< endl;
cout << endl;
cout << "NOTE: Notice the increase in remaining noise budget." << endl;
cout << "Decrypt encrypted_result (4(x^2+1)(x+1)^2)." << endl;
decryptor.decrypt(encrypted_result, decrypted_result);
cout << " + decryption of 4(x^2+1)(x+1)^2 = 0x" << decrypted_result.to_string() << " ...... Correct." << endl;
cout << endl;
/*
对于x=6,4(x^2+1)(x+1)^2 = 7252。由于明文模数设置为1024,因此此结果在模1024的整数范围内进行计算。因此,预期的输出应为7252 % 1024 == 84,或十六进制表示为0x54。
*/
/*
有时我们创建的自定义加密参数可能是无效的。Microsoft SEAL可以解释参数被视为无效的原因。在这里,我们只是简单地降低多项式模数的度数,使得参数不符合HomomorphicEncryption.org的安全标准。
*/
cout << "An example of invalid parameters" << endl;
parms.set_poly_modulus_degree(2048);
context = SEALContext(parms);
print_parameters(context);
cout << "Parameter validation (failed): " << context.parameter_error_message() << endl << endl;