如果不知道CKKS原理的话,不妨移步本专栏下的CKKS介绍文章。
本文通过PALISADE库的使用样例来进一步了解CKKS。
/*
* @file simple-real-numbers.cpp - Simple examples for CKKS.
* @author TPOC: [email protected]
*/
#define PROFILE
#include "palisade.h"
using namespace lbcrypto;
int main()
{
// 和TenSEAL一样,先建立加密的上下文,可以理解为一个包含了公私钥的大模块。
// 主要的一些参数
/* A1) Multiplicative depth:
* The CKKS scheme we setup here will work for any computation
* that has a multiplicative depth equal to 'multDepth'.
* This is the maximum possible depth of a given multiplication,
* but not the total number of multiplications supported by the
* scheme.
*
* For example, computation f(x, y) = x^2 + x*y + y^2 + x + y has
* a multiplicative depth of 1, but requires a total of 3 multiplications.
* On the other hand, computation g(x_i) = x1*x2*x3*x4 can be implemented
* either as a computation of multiplicative depth 3 as
* g(x_i) = ((x1*x2)*x3)*x4, or as a computation of multiplicative depth 2
* as g(x_i) = (x1*x2)*(x3*x4).
*
* For performance reasons, it's generally preferable to perform operations
* in the shorted multiplicative depth possible.
*/
// 这是(指定的)乘法深度。具体地,指一个具体加密方案允许的最大乘法次数。
uint32_t multDepth = 1;
/* A2) Bit-length of scaling factor.
* CKKS works for real numbers, but these numbers are encoded as integers.
* For instance, real number m=0.01 is encoded as m'=round(m*D), where D is
* a scheme parameter called scaling factor. Suppose D=1000, then m' is 10 (an
* integer). Say the result of a computation based on m' is 130, then at
* decryption, the scaling factor is removed so the user is presented with
* the real number result of 0.13.
*
* Parameter 'scaleFactorBits' determines the bit-length of the scaling
* factor D, but not the scaling factor itself. The latter is implementation
* specific, and it may also vary between ciphertexts in certain versions of
* CKKS (e.g., in EXACTRESCALE).
*
* Choosing 'scaleFactorBits' depends on the desired accuracy of the
* computation, as well as the remaining parameters like multDepth or security
* standard. This is because the remaining parameters determine how much noise
* will be incurred during the computation (remember CKKS is an approximate
* scheme that incurs small amounts of noise with every operation). The scaling
* factor should be large enough to both accommodate this noise and support results
* that match the desired accuracy.
*/
// 这是放大因子的比特数,即二进制位数。
// 当然也可以指定具体大小。比如用EXACTSCALE。
// 它取决于期望的精度、乘法深度以及拟定的安全标准。
// 这个参数需要足够大,以掩盖噪声和保证足够的精度。
uint32_t scaleFactorBits = 50;
/* A3) Number of plaintext slots used in the ciphertext.
* CKKS packs multiple plaintext values in each ciphertext.
* The maximum number of slots depends on a security parameter called ring
* dimension. In this instance, we don't specify the ring dimension directly,
* but let the library choose it for us, based on the security level we choose,
* the multiplicative depth we want to support, and the scaling factor size.
*
* Please use method GetRingDimension() to find out the exact ring dimension
* being used for these parameters. Give ring dimension N, the maximum batch
* size is N/2, because of the way CKKS works.
*/
// 这是向量的大小。CKKS对整个明文向量进行加密,但是这个向量的大小需要我们提前指定。
// 这个向量的大小需要为2的幂。比如,加密一个长度为8的向量:
uint32_t batchSize = 8;
/* A4) Desired security level based on FHE standards.
* This parameter can take four values. Three of the possible values correspond
* to 128-bit, 192-bit, and 256-bit security, and the fourth value corresponds
* to "NotSet", which means that the user is responsible for choosing security
* parameters. Naturally, "NotSet" should be used only in non-production
* environments, or by experts who understand the security implications of their
* choices.
*
* If a given security level is selected, the library will consult the current
* security parameter tables defined by the FHE standards consortium
* (https://homomorphicencryption.org/introduction/) to automatically
* select the security parameters. Please see "TABLES of RECOMMENDED PARAMETERS"
* in the following reference for more details:
* http://homomorphicencryption.org/wp-content/uploads/2018/11/HomomorphicEncryptionStandardv1.1.pdf
*/
// 这里支持128bit, 192bit和256bit的安全性。也可以让用户自己指定。
// 这里所谓的k-bit安全性是指,目前已知所有的对于CKKS方案的攻击都需要经过O(2^k)次比特运算才能奏效。
// 这个问题来自于RLWE问题的困难性。
// 安全性和一个大模数Q成反比关系,乘法深度约为大模数Q的比特数除以放大因子数的商。
// 一般的规律是,安全性数字越高,能做的乘法乘法深度越少。
SecurityLevel securityLevel = HEStd_128_classic;
// The following call creates a CKKS crypto context based on the
// arguments defined above.
CryptoContext cc =
CryptoContextFactory::genCryptoContextCKKS(
multDepth,
scaleFactorBits,
batchSize,
securityLevel);
cout << "CKKS scheme is using ring dimension " << cc->GetRingDimension() << endl << endl;
// Enable the features that you wish to use
cc->Enable(ENCRYPTION);
cc->Enable(SHE);
// B. Step 2 � Key Generation
/* B1) Generate encryption keys.
* These are used for encryption/decryption, as well as in generating different
* kinds of keys.
*/
// 这里生成加密和解密的密钥。
// auto关键字,用于自动推断变量的类型。
auto keys = cc->KeyGen();
/* B2) Generate the relinearization key
* In CKKS, whenever someone multiplies two ciphertexts encrypted with key s,
* we get a result with some components that are valid under key s, and
* with an additional component that's valid under key s^2.
*
* In most cases, we want to perform relinearization of the multiplicaiton result,
* i.e., we want to transform the s^2 component of the ciphertext so it becomes valid
* under original key s. To do so, we need to create what we call a relinearization
* key with the following line.
*/
// 这里需要基于密钥生成重线性化钥。
cc->EvalMultKeyGen(keys.secretKey);
/* B3) Generate the rotation keys
* CKKS supports rotating the contents of a packed ciphertext, but to do so, we
* need to create what we call a rotation key. This is done with the following call,
* which takes as input a vector with indices that correspond to the rotation offset
* we want to support. Negative indices correspond to right shift and positive to left
* shift. Look at the output of this demo for an illustration of this.
*
* Keep in mind that rotations work on the entire ring dimension, not the specified
* batch size. This means that, if ring dimension is 8 and batch size is 4, then an
* input (1,2,3,4,0,0,0,0) rotated by 2 will become (3,4,0,0,0,0,1,2) and not
* (3,4,1,2,0,0,0,0). Also, as someone can observe in the output of this demo, since
* CKKS is approximate, zeros are not exact - they're just very small numbers.
*/
// 这里生成旋转钥。根据指定的旋转方向和步数生成。
// 需要注意,这个旋转是相对于大的batchsize来说的。
// 比如说,[1,2,3,4,0,0,0,0]旋转是这个向量整体在做旋转,而不是仅限于前面四个值1,2,3,4之间的旋转。
cc->EvalAtIndexKeyGen(keys.secretKey, { 1, -2 });
// Step 3 � Encoding and encryption of inputs
// Inputs
// 可以看到,对应前面的batchsize = 8,我们就加密长度为8的向量。
vector> x1 = { 0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0 };
vector> x2 = { 5.0, 4.0, 3.0, 2.0, 1.0, 0.75, 0.5, 0.25 };
// Encoding as plaintexts
// 编码成多项式。
Plaintext ptxt1 = cc->MakeCKKSPackedPlaintext(x1);
Plaintext ptxt2 = cc->MakeCKKSPackedPlaintext(x2);
cout << "Input x1: " << ptxt1 << endl;
cout << "Input x2: " << ptxt2 << endl;
// Encrypt the encoded vectors
// 加密。
auto c1 = cc->Encrypt(keys.publicKey, ptxt1);
auto c2 = cc->Encrypt(keys.publicKey, ptxt2);
// Step 4 � Evaluation
// 加减乘除
// Homomorphic addition
auto cAdd = cc->EvalAdd(c1, c2);
// Homomorphic subtraction
auto cSub = cc->EvalSub(c1, c2);
// Homomorphic scalar multiplication
auto cScalar = cc->EvalMult(c1, 4.0);
// Homomorphic multiplication
auto cMul = cc->EvalMult(c1, c2);
// Homomorphic rotations
auto cRot1 = cc->EvalAtIndex(c1, 1);
auto cRot2 = cc->EvalAtIndex(c1, -2);
// Step 5 � Decryption and output
Plaintext result;
// We set the cout precision to 8 decimal digits for a nicer output.
// If you want to see the error/noise introduced by CKKS, bump it up
// to 15 and it should become visible.
// 这里似乎是设置输出格式为8位小数。
cout.precision(8);
cout << endl << "Results of homomorphic computations: " << endl;
// Decrypt the result of addition
cc->Decrypt(keys.secretKey, cAdd, &result);
result->SetLength(batchSize);
cout << "x1 + x2 = " << result << endl;
// Decrypt the result of subtraction
cc->Decrypt(keys.secretKey, cSub, &result);
result->SetLength(batchSize);
cout << "x1 - x2 = " << result << endl;
// Decrypt the result of scalar multiplication
cc->Decrypt(keys.secretKey, cScalar, &result);
result->SetLength(batchSize);
cout << "4 * x1 = " << result << endl;
// Decrypt the result of multiplication
cc->Decrypt(keys.secretKey, cMul, &result);
result->SetLength(batchSize);
cout << "x1 * x2 = " << result << endl;
// Decrypt the result of rotations
cc->Decrypt(keys.secretKey, cRot1, &result);
result->SetLength(batchSize);
cout << endl << "In rotations, very small outputs (~10^-10 here) correspond to 0's:" << endl;
cout << "x1 rotate by 1 = " << result << endl;
cc->Decrypt(keys.secretKey, cRot2, &result);
result->SetLength(batchSize);
cout << "x1 rotate by -2 = " << result << endl;
return 0;
}