当前位置: 首页 > 工具软件 > HElib > 使用案例 >

HElib-源码示例CKKS操作

栾和风
2023-12-01


代码来自HElib中CKKS的示例 HElib/examples/tutorial,将所有示例代码做了总结并加了一些注解。

初始化

//头文件、命名空间
#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中,私钥类是公钥类的子类,可以用上述方法创建一个公钥对象
  1. m 是"cyclotomic index",m必须是 2 的指数,当m增加,安全性增加,slot增多,但是执行速度变慢,密文大小增大
  2. bits 指定"ciphertext modulus"的比特位数,bits 增大,安全性降低,但可以执行更深层的同态计算,密文大小增大
  3. precision 指定了数据在编码、加密或解密时精度的位数,precision 增大,允许的同态计算的深度减小,但是安全性和运行效率不受影响,另外 precision 最好不要大于 40
  4. c 指定了key-switching matrices 的列数。c 增大,安全性会有一点增大,但是运行速度下降,公钥占用的内存增大。c至少为 2但最好不超过 8。

建议参数的设置要服从以下配置,可满足至少128位的安全性

mbitsc
163841192
327683586
327682993
327682392
655367258
655367176
655366694
655366133
655365582
13107214458
13107214356
13107213875
13107213294
13107212553
13107210982
26214429408
26214428706
26214427635
26214426464
26214425113
26214422342

加密

//第一种加密方式


  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增大,会降低密文的capacityaccuracy

  1. 密文的capacity一开始略小于创建Context对象时设定的bits参数,每次同态计算后,都会再下降,当capacity小于1,密文就不能被解密了。
  2. 密文的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>.

密文slot之间移位

  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

 类似资料: