0 摘要
本文主要分析OpenFHE库中测试用例所使用的代码,通过学习测试用例的编程思路,了解OpenFHE的使用方法以及调用逻辑。
这段代码采用了BFVrns方案以进行整数数据的同态运算,接下来我将会对各个段落的功能进行分析。
1 代码详解
1.1 引入OpenFHE库
#include "openfhe.h"
using namespace lbcrypto;
这部分代码引入了OpenFHE库,并使用其命名空间。
1.2 定义主函数
int main() {
定义主函数,在这部分进行同态加密操作。
1.3 设置加密参数
// Sample Program: Step 1: Set CryptoContext
CCParams<CryptoContextBFVRNS> parameters;
parameters.SetPlaintextModulus(65537);
parameters.SetMultiplicativeDepth(2);
CryptoContext<DCRTPoly> cryptoContext = GenCryptoContext(parameters);
cryptoContext->Enable(PKE);
cryptoContext->Enable(KEYSWITCH);
cryptoContext->Enable(LEVELEDSHE);
此代码段设置了加密上下文,指定了明文模数和乘法深度等参数,并启用了公钥加密(PKE)、密钥切换(KEYSWITCH)和分层同态加密(LEVELEDSHE)功能。
1.4 密钥生成
// Sample Program: Step 2: Key Generation
KeyPair<DCRTPoly> keyPair;
keyPair = cryptoContext->KeyGen();
cryptoContext->EvalMultKeyGen(keyPair.secretKey);
cryptoContext->EvalRotateKeyGen(keyPair.secretKey, {1, 2, -1, -2});
生成公私钥对、重新线性化密钥和旋转评估密钥。
1.5 加密
// Sample Program: Step 3: Encryption
std::vector<int64_t> vectorOfInts1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
Plaintext plaintext1 = cryptoContext->MakePackedPlaintext(vectorOfInts1);
std::vector<int64_t> vectorOfInts2 = {3, 2, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12};
Plaintext plaintext2 = cryptoContext->MakePackedPlaintext(vectorOfInts2);
std::vector<int64_t> vectorOfInts3 = {1, 2, 5, 2, 5, 6, 7, 8, 9, 10, 11, 12};
Plaintext plaintext3 = cryptoContext->MakePackedPlaintext(vectorOfInts3);
auto ciphertext1 = cryptoContext->Encrypt(keyPair.publicKey, plaintext1);
auto ciphertext2 = cryptoContext->Encrypt(keyPair.publicKey, plaintext2);
auto ciphertext3 = cryptoContext->Encrypt(keyPair.publicKey, plaintext3);
首先创建三个明文向量plaintext1、plaintext2、plaintext3,用于进行编码后加密形成密文。
用Encrypt函数对刚刚创建的3个向量进行加密处理。
这里可以看到,加密的过程需要keyPair.publickey也就是用刚刚生成的公私钥对中的公钥进行加密。
1.6 计算
// Sample Program: Step 4: Evaluation
auto ciphertextAdd12 = cryptoContext->EvalAdd(ciphertext1, ciphertext2);
auto ciphertextAddResult = cryptoContext->EvalAdd(ciphertextAdd12, ciphertext3);
auto ciphertextMul12 = cryptoContext->EvalMult(ciphertext1, ciphertext2);
auto ciphertextMultResult = cryptoContext->EvalMult(ciphertextMul12, ciphertext3);
auto ciphertextRot1 = cryptoContext->EvalRotate(ciphertext1, 1);
auto ciphertextRot2 = cryptoContext->EvalRotate(ciphertext1, 2);
auto ciphertextRot3 = cryptoContext->EvalRotate(ciphertext1, -1);
auto ciphertextRot4 = cryptoContext->EvalRotate(ciphertext1, -2);
在1.5中,代码把生成的长度为12的向量(plaintext1、plaintext2、plaintext3)加密后赋值给ciphertext1、ciphertext2、ciphertext3。
在计算过程中,先对ciphertext1、ciphertext2进行密文加运算,将运算结果赋值给ciphertextAdd12,然后用ciphertextAdd12与ciphertext3再次进行密文加运算,得到ciphertext1、ciphertext2、ciphertext3密文加的结果,并赋值给ciphertextAddResult。
同理,下面乘法运算也是分步骤进行的,先对ciphertext1、ciphertext2进行密文乘运算,后利用第一步得出的结果再次和ciphertext3进行密文乘运算,得出密态的结果,赋值给ciphertextMultResult。
接下来就是对ciphertext1的四次轮转操作,在EvalRotate函数中,第一个值为要轮转的向量,第二个值为轮转的方向,正数轮转代表向量向左轮转(左移),负数轮转代表向量向右轮转(右移)。
这样说很抽象,举个例子:
假设我们有一个向量 α[1, 2, 3, 4, 5],我们对这个向量进行不同的轮转操作:
1.EvalRotate(α, 1)=[2, 3, 4, 5, 1]
EvalRotate的意义是向左轮转一位,2会取代原来1的位置,3取代2,以此类推,最左边的1则会到最右边去。可以把向量的轮转看成是一个循环队列,轮转后队头可能成为队尾,队尾也可能成为新的队头。
2.EvalRotate(α, -1)=[5, 1, 2, 3, 4]
很明显,这是上一个函数的逆运算,即向右轮转一位,跟上面说的一样,位于队尾的5经过轮转之后会跑到队头的位置,剩下4个元素依次向右轮转。
3.EvalRotate(α, 2)=[3, 4, 5, 1, 2]
向左轮转两位,第一次轮转结果[2, 3, 4, 5, 1],第二次轮转得到最终结果[3, 4, 5, 1, 2]。
4.EvalRotate(α, -2)=[4, 5, 1, 2, 3]
向右轮转两位,第一次轮转结果[5, 1, 2, 3, 4],第二次轮转得到最终结果[4, 5, 1, 2, 3]。
至此,相信你对密文的加乘以及轮转已经有了一定的认识,让我们看看下面的代码写了些什么。
1.7 解密
// Sample Program: Step 5: Decryption
Plaintext plaintextAddResult;
cryptoContext->Decrypt(keyPair.secretKey, ciphertextAddResult, &plaintextAddResult);
Plaintext plaintextMultResult;
cryptoContext->Decrypt(keyPair.secretKey, ciphertextMultResult, &plaintextMultResult);
Plaintext plaintextRot1;
cryptoContext->Decrypt(keyPair.secretKey, ciphertextRot1, &plaintextRot1);
Plaintext plaintextRot2;
cryptoContext->Decrypt(keyPair.secretKey, ciphertextRot2, &plaintextRot2);
Plaintext plaintextRot3;
cryptoContext->Decrypt(keyPair.secretKey, ciphertextRot3, &plaintextRot3);
Plaintext plaintextRot4;
cryptoContext->Decrypt(keyPair.secretKey, ciphertextRot4, &plaintextRot4);
plaintextRot1->SetLength(vectorOfInts1.size());
plaintextRot2->SetLength(vectorOfInts1.size());
plaintextRot3->SetLength(vectorOfInts1.size());
plaintextRot4->SetLength(vectorOfInts1.size());
首先,声明一个名为plaintextAddResult的明文对象,用于存储解密后的加法结果。
再声明一个名为plaintextMultResult的明文对象,用于存储解密后的乘法结果。
调用 Decrypt 方法使用私钥 keyPair.secretKey 解密密文 ciphertextAddResult,并将结果存储在 plaintextAddResult 中。
乘法与加法同理,调用 Decrypt 方法使用私钥 keyPair.secretKey 解密密文 ciphertextMultResult,并将结果存储在 plaintextMultResult 中。
随后声明四个明文变量plaintext1、plaintext2、plaintext3、plaintext4,用来存储轮转计算后的明文结果。
调用 Decrypt 方法使用私钥 keyPair.secretKey 分别解密密文 ciphertextRot1、ciphertextRot2、ciphertextRot3 和 ciphertextRot4,并将结果分别存储在 plaintextRot1、plaintextRot2、plaintextRot3 和 plaintextRot4 中。
最后这几行代码将 plaintextRot1 到 plaintextRot4 的长度设置为 vectorOfInts1 的大小。这样确保解密后的明文长度与原始向量一致。
1.8 输出结果
std::cout << "Plaintext #1: " << plaintext1 << std::endl;
std::cout << "Plaintext #2: " << plaintext2 << std::endl;
std::cout << "Plaintext #3: " << plaintext3 << std::endl;
std::cout << "\nResults of homomorphic computations" << std::endl;
std::cout << "#1 + #2 + #3: " << plaintextAddResult << std::endl;
std::cout << "#1 * #2 * #3: " << plaintextMultResult << std::endl;
std::cout << "Left rotation of #1 by 1: " << plaintextRot1 << std::endl;
std::cout << "Left rotation of #1 by 2: " << plaintextRot2 << std::endl;
std::cout << "Right rotation of #1 by 1: " << plaintextRot3 << std::endl;
std::cout << "Right rotation of #1 by 2: " << plaintextRot4 << std::endl;
return 0;
}
这一步不需要做过多的解释,就是把刚才赋值好的变量输出到控制台。
2 总结
本文是对OpenFHE中测试用例simple-intergers做出详细解释,简而言之,先预定义3个向量作为密态运算的项,随后进行密态加法、密态乘法、密态轮转三种运算,最后将3种运算的结果进行解密,最后输出。
这个测试用例只是简单的对OpenFHE中最基础的几个函数进行调用,然后把密态运算的结果解密后输出,对于测试用例而言比较完整,但是根据这个代码框架,可以玩的东西一下就变得很多了。
比如可以添加交互代码,让用户自己输入3个数字,然后对这三个数字进行上述的运算,随后解密输出,这也是我能想到的比较初级的玩法,需要解决的问题是如何让密态运算变得可见,我认为思路是先将未解密的密文输出到控制台,让用户有“密态”运算的概念。
现在的成果是比较直观的,我认为作为一个用户而言运算过程中的密文处理是透明的,用户只是看到了简单的“计算器”功能,这一点我最近也在思考如何让密文在运算中“可见”,可能在不久的将来会用代码实现。
如果你对我的内容感兴趣,请点赞收藏,这是我更新的最大动力!