//头文件、命名空间
#include <helib/helib.h>
using namespace std;
using namespace helib;
Context context =
ContextBuilder<CKKS>().m(32 * 1024).bits(358).precision(20).c(6).build();
//使用builder初始化一个Context对象,具体参数在后面详细说明
cout << "securityLevel=" << context.securityLevel() << "\n";
//打印出估计的安全等级
long n = context.getNSlots();
//获取slot的个数,n=m/4
//在CKKS方案中,明文是实数(复数)的向量,向量的长度为n,n的大小有参数决定。向量的每个组成称为slot,下标为{0,1,2,n-1},n即为slot的个数
SecKey secretKey(context);
secretKey.GenSecKey();
//创建一个私钥对象,要和指定的context联系在一起
const PubKey& publicKey = secretKey;
//在HElib中,私钥类是公钥类的子类,可以用上述方法创建一个公钥对象
- m 是"cyclotomic index",m必须是 2 的指数,当m增加,安全性增加,slot增多,但是执行速度变慢,密文大小增大
- bits 指定"ciphertext modulus"的比特位数,bits 增大,安全性降低,但可以执行更深层的同态计算,密文大小增大
- precision 指定了数据在编码、加密或解密时精度的位数,precision 增大,允许的同态计算的深度减小,但是安全性和运行效率不受影响,另外 precision 最好不要大于 40
- c 指定了key-switching matrices 的列数。c 增大,安全性会有一点增大,但是运行速度下降,公钥占用的内存增大。c至少为 2但最好不超过 8。
建议参数的设置要服从以下配置,可满足至少128位的安全性
m bits c 16384 119 2 32768 358 6 32768 299 3 32768 239 2 65536 725 8 65536 717 6 65536 669 4 65536 613 3 65536 558 2 131072 1445 8 131072 1435 6 131072 1387 5 131072 1329 4 131072 1255 3 131072 1098 2 262144 2940 8 262144 2870 6 262144 2763 5 262144 2646 4 262144 2511 3 262144 2234 2
//第一种加密方式
vector<double> v(n);
for (long i = 0; i < n; i++)
v[i] = sin(2.0 * PI * i / n);
//声明一个长度为n的vector,赋予任意值。PI由HElib定义
PtxtArray p(context, v);
//将明文vector装进一个特殊类型的容器PtxtArray。PtxtArray要和Context对象关联
//很多类型的vector都可以装进PtxtArray对象(int, long, double, complex<double>)
//上述过程也可分两步:PtxtArray p(context); p.load(v);
//其实这里做了encoding的工作
Ctxt c(publicKey);
//创建密文,要和公钥关联
p.encrypt(c);
//加密p存入c
//由于密文总是关联一个公钥,所以加密的时候不用传公钥
cout << "c.capacity=" << c.capacity() << " ";
cout << "c.errorBound=" << c.errorBound() << "\n";
//输出密文的capacity和absolute error
depth
在CKKS方案中,密文存在一定的“noise”,同态计算的层次加深,noise增大,会降低密文的capacity和accuracy。
- 密文的capacity一开始略小于创建Context对象时设定的bits参数,每次同态计算后,都会再下降,当capacity小于1,密文就不能被解密了。
- 密文的accuracy计算的是ciphertext c 对比于应该加密的plaintext p 的absolute error,该值不能大于2^{-precision},precision是创建Context对象时设定的,每次同态计算后,absolute error都会增大
//第二种加密方式
PtxtArray p(context);
//创建一个PtxtArray对象关联Context对象
p.random();
//用随机数填充n个slots,interval [0,1]
Ctxt c(publicKey);
p.encrypt(c);
//加密p存入c
//===================================================
//加密一群随机密文
int len = 3;
vector<PtxtArray> p, q;
//创建元素为PtxtArray对象的vector
for (int i = 0; i < len; i++) {
p.emplace_back(context);
p[i].random();
q.emplace_back(context);
q[i].random();
}
//每个PtxtArray对象都要和Context对象关联
// p[i] is a random PtxtArray for i = 0..len-1
// q[i] is a random PtxtArray for i = 0..len-1
vector<Ctxt> c, d;
for (int i = 0; i < len; i++) {
c.emplace_back(publicKey);
p[i].encrypt(c[i]);
d.emplace_back(publicKey);
q[i].encrypt(d[i]);
}
// c[i] encrypts p[i] for i = 0..len-1
// d[i] encrypts q[i] for i = 0..len-1
PtxtArray pp(context);
//创建一个PtxtArray对象
pp.decrypt(c, secretKey);
//私钥解密c存入pp
vector<double> v;
pp.store(v);
//pp存入标准的vector类型,可以没有这个操作
double distance = Distance(p, pp);
cout << "distance=" << distance << "\n";
//计算原始明文和解密后的明文之间的距离
// For debugging, you can also make "approximate" comparisons as follows:
if (pp == Approx(p))
cout << "GOOD\n";
else
cout << "BAD\n";
// Here, p is the "correct value" and you want to test if pp is "close" to it.
// NOTES: The Approx function (which is really a class constructor) takes two
// optional arguments:
// double tolerance; // default is 0.01
// double floor; // default is 1.0
//
// The expression
// a == Approx(b, tolerance, floor)
// is true iff Distance(a,b) <= tolerance*max(Norm(b),floor), The idea is
// that it checks if the relative error is at most tolerance, unless Norm(b)
// itself is too small (as determined by floor). Here, Norm(b) is the max
// absolute value of the slots, and Distance(a,b) = Norm(a-b).
//
// In addition to PtxtArray's, you can compare values of type double or
// complex<double>, and vectors of type double or complex<double>.
Context context =
ContextBuilder<CKKS>().m(32 * 1024).bits(358).precision(30).c(6).build();
cout << "securityLevel=" << context.securityLevel() << "\n";
long n = context.getNSlots();
SecKey secretKey(context);
secretKey.GenSecKey();
//以上是正常的初始化步骤
addSome1DMatrices(secretKey);
//为了进行移位操作,需要加上这个
//密钥类是公钥类的子类,addSome1DMatrices的调用需要数据存在密钥中,但是计算出的信息存在公钥中
// Recall that SecKey is a subclass of PubKey. The call to addSome1DMatrices
// needs data stored in the secret key, but the information it computes is
// stored in the public key.
const PubKey& publicKey = secretKey;
//创建公钥对象
//循环移位
rotate(c, 2);
// rotate c right by 2:
// (c[0], ..., c[n-1]) = (c[n-2], c[n-1], c[0], c[1], ..., c[n-3])
rotate(c, -1);
// rotate c left by 1
// (c[0], ..., c[n-1]) = (c[1], c[2], ..., c[n-1], c[0])
//移位,缺位补0
shift(c, 2);
// rotate c right by 2:
// (c[0], ..., c[n-1]) = (0, 0, c[0], c[1], ..., c[n-3])
shift(c, -1);
// rotate c left by 1
// (c[0], ..., c[n-1]) = (c[1], c[2], ..., c[n-1], 0)
//所有slot求和的结果放入每个slot
// We can also sum all of slots, leaving the sum in each slot
totalSums(c);
// (c[0], ..., c[n-1]) = (S, ..., S), where S = sum_{i=0}^{n-1} c[i]
//需要加入头文件
#include <helib/matmul.h>
···正常初始化···
MatMul_CKKS mat(context,
[n](long i, long j) { return ((i + j) % n) / double(n); });
//定义一个n×n的明文矩阵
//第二个参数代表传两个long型返回double型
//输入(i, j),返回matrix in row i and column j的值
Ctxt c0 = c;
c0 *= mat;
//密文乘矩阵,c的slot看做行向量
//如果给定的矩阵要使用多次,可以做pre-computation来提高运行效率
EncodedMatMul_CKKS emat(mat);
//一次pre-computation通过"encoding"矩阵来执行
Ctxt c1 = c;
c1 *= emat;
emat.upgrade();
//做更多的pre-computation来提高运行效率
//计算 e = sum_{i=0}^{len-1} c[i]*d[i]
for (int i = 0; i < len; i++) {
Ctxt tmp = c[i];
tmp.multLowLvl(d[i]);
// tmp is now c[i]*d[i] but in a non-canonical state
e += tmp;
// e is now c[0]*d[0] + ... c[i]*d[i] but in a non-canonical state
}
e.reLinearize();
//将e转化为canonical state。
//在本例中这不是必须的,如果e还要用于其他操作,转化为canonical state会使计算效率更高
在CKKS方案中,密文相乘实际上有两步:
ctxt1.multLowLvl(ctxt2);
和ctxt1.reLinearize();
第一步会使ctxt1是non-canonical state,经过第二步转换为canonical state
但是在某些操作例如密文加法,能够在non-canonical state直接相加,得到的结果仍为non-canonical state