2021SC@SDUSC
目录
介绍
在这一篇博客中,我们主要是介绍了利用PALISADE的主要API来进行CKKS的同态加密解密的大体步骤以及部分代码,如果想要看完整代码和运行过程及结果的同学,请查看的我的下一篇博客 PALISADE开源库(三)CKKS代码分析(二)简单代码运行及结果 来进行具体的操作。
大体步骤
1. 设置CryptoContext类
1)指定乘法深度
我们在这里设置的CKKS方案将适用于任何具有乘数深度等于" multidepth "的计算。这是给定乘法的最大可能深度,但不是该方案支持的乘法总数。
例如,f(x, y) = x^2 + x*y + y^2 + x + y的乘法深度为1,但总共需要3次乘法。另一方面,计算 g(x_i) = x1*x2*x3*x4 可以实现为 g(x_i) = ((x1*x2)*x3)*x4的乘法深度为3的计算,也可以实现为乘深2的计算:g(x_i) = (x1*x2)*(x3*x4)。
出于性能方面的考虑,通常更可取的做法是尽可能缩短乘法深度。
uint32_t multDepth = 1;
2)指定缩放因子的位长
CKKS适用于实数,但这些数字被编码为整数。例如,实数m = 0.01被编码为m' = round(m*D),其中D是称为比例因子的方案参数。假设D = 1000,那么 m' 是 10(一个整数)。假设一个基于 m' 的计算结果是130,然后在解密时,去掉比例因子,使用户看到的实数结果是0.13。
参数 " scaleFactorBits " 决定了比例因子 D 的位长,而不是比例因子本身。后者是特定于实现的,在CKKS的某些版本(例如EXACTRESCALE)中,它也可能在密文之间有所不同。
选择 " scaleFactorBits " 取决于所需的计算精度,以及剩余的参数,如 multidepth 或 security 。这是因为其余的参数决定了在计算过程中会产生多少噪声 (记住CKKS是一种近似方案,每次操作都会产生少量的噪声)。缩放因子应该足够大,既能容纳这种噪声,又能支持与所需精度匹配的结果。
uint32_t scaleFactorBits = 50;
3)指定密文中使用的明文槽数
CKKS在每个密文中包含多个明文值。插槽的最大数目取决于称为环尺寸的安全参数。在本例中,我们不直接指定环的尺寸,而是让库根据我们选择的安全级别、我们希望支持的乘深和比例因子大小为我们选择环的尺寸。
请使用 GetRingDimension() 方法找出用于这些参数的环的确切尺寸。根据CKKS的工作方式,给出环尺寸N,最大批量尺寸为N/2。
这就是我上一个系列文章中提到的M和N的来源。
uint32_t batchSize = 8;
4)指定基于FHE标准的期望安全级别
这个参数可以有四个值。三个可能的值分别对应128位、192位和256位的安全性,第四个值对应“ NotSet ”,这意味着用户负责选择安全性参数。当然,“NotSet”应该只在非生产环境中使用,或者由了解其选择的安全性影响的专家使用。
如果选择了给定的安全级别,库将参考FHE标准联盟(Introduction – Homomorphic Encryption Standardization) 定义的当前安全参数表,自动选择安全参数。详情请参阅以下参考资料中的“推荐参数表”:http://homomorphicencryption.org/wp-content/uploads/2018/11/HomomorphicEncryptionStandardv1.1.pdf
SecurityLevel securityLevel = HEStd_128_classic;
5)基于指定参数加密上下文
下面的调用基于上面定义的参数创建一个CKKS加密上下文。
CryptoContext<DCRTPoly> cc =
CryptoContextFactory<DCRTPoly>::genCryptoContextCKKS(
multDepth, scaleFactorBits, batchSize, securityLevel);
std::cout << "CKKS scheme is using ring dimension " << cc->GetRingDimension()
<< std::endl
<< std::endl;
启用您希望使用的特性
cc->Enable(ENCRYPTION);
cc->Enable(SHE);
2. 密钥生成
1)生成加密密钥
它们用于加密/解密,以及生成不同类型的密钥。
auto keys = cc->KeyGen();
2)生成重新线性化键
在CKKS中,每当有人将两个用密钥s加密的密文相乘时,我们得到一个结果,其中一些组件在密钥s下是有效的,另一个组件在密钥s^2下是有效的。
在大多数情况下,我们想执行密文乘密文的结果,也就是说,我们要变换的s ^ 2分量密文所以变得有效在最初的关键。为此,我们需要创建 relinearization 键。
cc->EvalMultKeyGen(keys.secretKey);
3)生成旋转键
CKKS支持旋转打包密文的内容,但要这样做,我们需要创建一个我们称为旋转密钥的东西。这是通过下面的调用完成的,它接受一个向量作为输入,该向量的下标对应于我们想要支持的旋转偏移量。负指标对应右移,正指标对应左移。
cc->EvalAtIndexKeyGen(keys.secretKey, {1, -2});
3. 输入后的编码和加密
输入:
vector<double> x1 = {0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0};
vector<double> x2 = {5.0, 4.0, 3.0, 2.0, 1.0, 0.75, 0.5, 0.25};
编码为明文:
Plaintext ptxt1 = cc->MakeCKKSPackedPlaintext(x1);
Plaintext ptxt2 = cc->MakeCKKSPackedPlaintext(x2);
输出编码后的明文:
std::cout << "Input x1: " << ptxt1 << std::endl;
std::cout << "Input x2: " << ptxt2 << std::endl;
加密编码向量:
auto c1 = cc->Encrypt(keys.publicKey, ptxt1);
auto c2 = cc->Encrypt(keys.publicKey, ptxt2);
4. 进行加减乘等操作
密文相加:
auto cAdd = cc->EvalAdd(c1, c2);
密文相减:
auto cSub = cc->EvalSub(c1, c2);
明文与密文想乘:
auto cScalar = cc->EvalMult(c1, 4.0);
密文相乘:
auto cMul = cc->EvalMult(c1, c2);
密文旋转:
auto cRot1 = cc->EvalAtIndex(c1, 1);
auto cRot2 = cc->EvalAtIndex(c1, -2);
5. 解密和输出
Plaintext result;
为了更好的输出,我们将cout精度设置为8位十进制数字。
如果你想看到CKKS引入的错误/噪声,将其增加到15,它就会变得很明显了。
std::cout.precision(8);
std::cout << std::endl
<< "Results of homomorphic computations: " << std::endl;
解密加法的结果:
cc->Decrypt(keys.secretKey, cAdd, &result);
result->SetLength(batchSize);
std::cout << "x1 + x2 = " << result;
std::cout << "Estimated precision:" << result->GetLogPrecision()
<< std::endl;
解密减法的结果:
cc->Decrypt(keys.secretKey, cSub, &result);
result->SetLength(batchSize);
std::cout << "x1 - x2 = " << result;
std::cout << "Estimated precision: " << result->GetLogPrecision()
<< std::endl;
解密明文乘密文的结果:
cc->Decrypt(keys.secretKey, cScalar, &result);
result->SetLength(batchSize);
std::cout << "4 * x1 = " << result;
std::cout << "Estimated precision: " << result->GetLogPrecision()
<< std::endl;
解密密文乘密文的结果:
cc->Decrypt(keys.secretKey, cMul, &result);
result->SetLength(batchSize);
std::cout << "x1 * x2 = " << result;
std::cout << "Estimated precision: " << result->GetLogPrecision()
<< std::endl;
解密旋转的结果:
cc->Decrypt(keys.secretKey, cRot1, &result);
result->SetLength(batchSize);
std::cout
<< std::endl
<< "In rotations, very small outputs (~10^-10 here) correspond to 0's:"
<< std::endl;
std::cout << "x1 rotate by 1 = " << result;
std::cout << "Estimated precision: " << result->GetLogPrecision()
<< std::endl;
cc->Decrypt(keys.secretKey, cRot2, &result);
result->SetLength(batchSize);
std::cout << "x1 rotate by -2 = " << result << std::endl;
std::cout << "Estimated precision: " << result->GetLogPrecision()
<< std::endl;