0%

Pyfhel学习:CKKS浮点数加密

Pyfhel库中包含的内容十分丰富,可以从Pyfhel/examples入手,来体验如何使用Pyfhel。下面具体展示其中的CKKS方案使用实例。

CKKS实例

CKKS(Cheon-Kim-Kim-Song)加密方案在同态加密领域应用广泛,尤其在处理实数和复数计算方面表现优越。CKKS方案能够对编码后的近似实数或复数进行加密,并支持同态加密操作(如加法和乘法),最终解密出近似结果。该方案特别适用于需要处理大量实数数据的机器学习和数据分析场景。论文:CKKS

声明参数

1
2
3
4
5
6
7
HE = Pyfhel()
ckks_params = {
'scheme': 'CKKS',
'n': 2**14,
'scale': 2**30,
'qi_sizes': [60, 30, 30, 30, 60]
}
  • ‘n’:环的度数。环度指的是同态加密使用的多项式的最大次数加1,因为多项式的次数是从0开始的。这里的值是$2^{14}$,意味着多项式最大次数为$2^{14} - 1$。可以看成是明文数组的长度(明文槽的数量)。对于CKKS,$n/2$个元素会被编码为一个单一的密文

    注意:环度越大,表示能处理的数据量也越大,但计算复杂度也会增加

  • ‘ scale’:缩放因子。将浮点数转换为定点数,因为浮点数不能直接进行计算

  • ‘qi_sizes’:链中每个素数的比特数

构造密钥

1
2
3
4
5
6
7
8
9
10
11
# 生成CKKS方案的context
HE.contextGen(**ckks_params)

# 生成公/私钥对
HE.keyGen()

# 生成旋转密钥 --> 允许旋转/移动
HE.rotateKeyGen()

# 生成重线性化密钥
HE.relinKeyGen()

生成测试数据

  • 定义两个1D的浮点型数组,编码并加密
1
2
3
4
5
6
7
8
9
10
11
12
# 生成浮点型数组
arr_x = np.array([0.1, 0.2, -0.3], dtype=np.float64)
arr_y = np.array([-1.5, 2.3, 4.7], dtype=np.float64)

# 将arr_x和arr_y编码为PyPtxt的明文
# 用0填充为长度n,再编码为PyPtxt的明文
ptxt_x = HE.encodeFrac(arr_x)
ptxt_y = HE.encodeFrac(arr_y)

# 加密明文ptxt_x和ptxt_y,返回PyCtxt密文
ctxt_x = HE.encryptPtxt(ptxt_x)
ctxt_y = HE.encryptPtxt(ptxt_y)

同态计算

密文/明文必须建立在同一个context下

  • 密文-密文操作

    1
    2
    3
    4
    5
    6
    7
    ccSum = ctxt_x + ctxt_y
    ccSub = ctxt_x - ctxt_y
    ccMul = ctxt_x * ctxt_y
    cNeg = -ctxt_x
    cSq = ctxt_x ** 2
    cRotR = ctxt_x >> 2
    cRotL = ctxt_x << 2
  • 密文-明文操作

    1
    2
    3
    cpSum = ctxt_x + ptxt_y
    cpSub = ctxt_x - ptxt_y
    cpMul = ctxt_x * ptxt_y
  • 乘法重线性化:密文-密文之间的乘法会导致生成的密文多项式的次数增加。为了防止这种增长,通过使用重现性化技术(通常在每次c-c mult之后),从而降低密文多项式的次数到最小规模(两个多项式c0 & c1)

    1
    2
    3
    4
    print("Relinearization-> Right after each multiplication.")
    print(f"ccMul before relinearization (size {ccMul.size()}): {ccMul}")
    ~ccMul
    print(f"ccMul after relinearization (size {ccMul.size()}): {ccMul}")
  • 重缩放&模切换

    • CKKS 进行更复杂的操作需要跟踪 CKKS 缩放因子

    • 使用两个 CKKS 密文(或一个密文和一个明文)进行操作需要它们具有相同的缩放因子和相同的模数级别

    • 缩放因子:乘法产生一个新的缩放因子,它是两个操作数比例的乘积。要缩小密文,请使用 HE.rescale_to_next(ctxt) 函数,该函数将模数切换到 qi 链中的下一个模数,并将密文除以前一个模数。由于这是唯一的缩小操作,建议使用与HE.qi_sizes中的中间模数大小相同的“scale_bits”

    • 模切换:切换到 qi 链中的下一个模数,但不重新缩放。这是通过 HE.mod_switch_to_next(ctxt)函数实现的

    • 为了方便用户,Pyfhel 提供了 HE.align_mod_n_scale(this, other),它会自动执行重新缩放和模数切换。PyCtxt 的所有 2 -输入重载运算符 (+、-、*、/) 都会自动调用此函数。
      相应的 HE.add、HE.sub、HE.multiply 则不会

    • 在此示例中,计算均方误差,将两个密文的平均值视为真实分布。逐步检查比例和模级:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      #  1. Mean
      c_mean = (ctxt_x + ctxt_y) / 2
      # 2. MSE
      c_mse_1 = ~((ctxt_x - c_mean)**2)
      c_mse_2 = (~(ctxt_y - c_mean)**2)
      c_mse = (c_mse_1 + c_mse_2)/ 3
      # 3. Cumulative sum
      c_mse += (c_mse << 1)
      c_mse += (c_mse << 2) # element 0 contains the result
      print("\n5. Rescaling & Mod Switching.")
      print("->\tMean: ", c_mean)
      print("->\tMSE_1: ", c_mse_1)
      print("->\tMSE_2: ", c_mse_2)
      print("->\tMSE: ", c_mse)

解码并解密

  • 将密文信息进行解码&解密,并进行显示输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
r_x    = HE.decryptFrac(ctxt_x)
r_y = HE.decryptFrac(ctxt_y)
rccSum = HE.decryptFrac(ccSum)
rccSub = HE.decryptFrac(ccSub)
rccMul = HE.decryptFrac(ccMul)
rcSq = HE.decryptFrac(cSq )
rcNeg = HE.decryptFrac(cNeg )
rcRotR = HE.decryptFrac(cRotR)
rcRotL = HE.decryptFrac(cRotL)
rcpSum = HE.decryptFrac(cpSum)
rcpSub = HE.decryptFrac(cpSub)
rcpMul = HE.decryptFrac(cpMul)
rmean = HE.decryptFrac(c_mean)
rmse = HE.decryptFrac(c_mse)

# 结果为近似值!如果你增加小数,你会注意到误差
_r = lambda x: np.round(x, decimals=6)
print("6. Decrypting results")
print(" Original ciphertexts: ")
print(" ->\tctxt_x --(decr)--> ", _r(r_x))
print(" ->\tctxt_y --(decr)--> ", _r(r_y))
print(" Ciphertext-ciphertext Ops: ")
print(" ->\tctxt_x + ctxt_y = ccSum --(decr)--> ", _r(rccSum))
print(" ->\tctxt_x - ctxt_y = ccSub --(decr)--> ", _r(rccSub))
print(" ->\tctxt_x * ctxt_y = ccMul --(decr)--> ", _r(rccMul))
print(" Single ciphertext: ")
print(" ->\tctxt_x**2 = cSq --(decr)--> ", _r(rcSq ))
print(" ->\t- ctxt_x = cNeg --(decr)--> ", _r(rcNeg ))
print(" ->\tctxt_x >> 4 = cRotR --(decr)--> ", _r(rcRotR))
print(" ->\tctxt_x << 4 = cRotL --(decr)--> ", _r(rcRotL))
print(" Ciphertext-plaintext ops: ")
print(" ->\tctxt_x + ptxt_y = cpSum --(decr)--> ", _r(rcpSum))
print(" ->\tctxt_x - ptxt_y = cpSub --(decr)--> ", _r(rcpSub))
print(" ->\tctxt_x * ptxt_y = cpMul --(decr)--> ", _r(rcpMul))
print(" Mean Squared error: ")
print(" ->\tmean(ctxt_x, ctxt_y) = c_mean --(decr)--> ", _r(rmean))
print(" ->\tmse(ctxt_x, ctxt_y) = c_mse --(decr)--> ", _r(rmse))

结语

以上实例具体在Pyfhel/examples中,有需要可以自行查阅源码。

-------------    本文结束  感谢阅读    -------------