2021SC@SDUSC PALISADE开源库(二)CKKS讲解系列(二)完整编码和解码
2021SC@SDUSC目录介绍CKKS编码相关代码执行(重要)将输入的实数进行共轭处理介绍在上一篇PALISADE开源库(二)CKKS讲解系列(一)普通编码和解码中,我们了解到,要实现CKKS加密方法和对加密复数向量的计算,我们必须构建一个编码器和一个解码器,将我们的复数向量转化为多项式。这个编码器——解码器步骤是必要的,因为加密、解密和其他机制也同样适用于多项式。因此,有必要有一种方法将我们的
2021SC@SDUSC
目录
介绍
在上一篇PALISADE开源库(二)CKKS讲解系列(一)普通编码和解码中,我们了解到,要实现CKKS加密方法和对加密复数向量的计算,我们必须构建一个编码器和一个解码器,将我们的复数向量转化为多项式。
这个编码器——解码器步骤是必要的,因为加密、解密和其他机制也同样适用于多项式。因此,有必要有一种方法将我们的实数值向量转化为多项式。
在本文中,我们将探讨如何实现原始论文Homomorphic Encryption for Arithmetic of Approximate Numbers 中使用的编码器和解码器,这将是我们从头开始实现 CKKS 的第一步。
CKKS编码和解码
编码:Message -> m(X)
Message是一个n/2维的复向量。我们在拿到一个复向量Message∈C^(n/2)之后,对他取一下共轭并且将原向量和共轭拼接在一起,得到增广的Message'∈C^n。
举个例子,我们拿到了一个复向量(3 +4j, 2 - 1j)。按照上面的做法,我们增广为:
(3 + 4j, 2 - 1j, 3 - 4j, 2 + 1j)
考虑复数域内多项式 X^n +1,它有n个复数根ξi,记这些复数根组成的向量为ξ,并且前n/2个根和后n/2个根也是共轭的。
下面,我们求一个整数系数的插值多项式m ( X ) ,使得m ( ξ ) ≈ Δ×Message'i 。即把 X^n + 1 = 0的根作为自变量丢到 m里面去,使得输出的值是Message的对应分量。
由于共轭性质存在,插值出来的 m 的系数都是实数。但是,CKKS最后要对整数进行操作,因此在这里我们引入放大因子 Δ ,将Message的数值放大之后再进行取整。这样的话,可以尽可能保留小数的位数,提高加密的精度。显然,如果直接对 m 系数取整,误差会比较大。
m就是对消息编码的结果。
对编码结果做存储时,我们只需要存储 m 的系数即可。
解码:Message <- m(X)
把上述步骤倒过来就行了。我们已知了 m ,接下来把 ξ 带入就完事了。
最后别忘了除以 Δ 就行。
相关代码执行(重要)
现在我们终于知道了CKKS的编码和解码是如何工作的,下面我们来实现代码吧!
将输入的实数进行共轭处理
import numpy as np
# 我们先设置一下参数
M = 8
class CKKSEncoder:
""" 基本CKKS编码器将编码变为多项式。"""
def __init__(self, M: int):
"""初始化编码器M的一个2的幂。"""
"""xi,是单位的第m次根,将作为我们计算的基础。"""
self.xi = np.exp(2 *np.pi * 1j / M)
self.M = M
def pi(self, z: np.array) -> np.array:
"""将H的向量投影到C ^ {N / 2}中"""
N = self.M // 4
return z[:N]
def pi_inverse(self, z: np.array) -> np.array:
"""将向量C ^ {N / 2}展开复共轭。"""
z_conjugate = z[::-1]
z_conjugate = [np.conjugate(x) for x in z_conjugate]
return np.concatenate([z, z_conjugate])
encoder = CKKSEncoder(M)
# 现在我们可以用添加的方法初始化编码器
encoder = CKKSEncoder(M)
z = np.array([0, 1])
p = np.array([3 +4j, 2 - 1j])
print(encoder.pi_inverse(z))
print(encoder.pi_inverse(p))
将万德蒙矩阵通过数组呈现出来
@staticmethod
def vandermonde(xi: np.complex128, M: int) -> np.array:
"""从单位的m次根计算万德蒙矩阵。"""
N = M // 2
matrix = []
# 我们将生成矩阵的每一行
for i in range(N):
# 每一行我们选择一个不同的根
root = xi ** (2 * i + 1)
row = []
# 然后我们储存它的权值
for j in range(N):
row.append(root ** j)
matrix.append(row)
return matrix
def create_sigma_R_basis(self):
self.sigma_R_basis = np.array(self.vandermonde(self.xi, self.M)).T
print(encoder.sigma_R_basis)
注意,这矩阵输出的时候,进行了转置
检查元素是否被编码为整数多项式
coordinates = [1,1,1,1]
b = np.matmul(encoder.sigma_R_basis.T, coordinates)
print(b)
p = encoder.sigma_inverse(b)
print(p)
编码器和解码器相关代码
def compute_basis_coordinates(self, z):
"""计算向量相对于正交基的坐标。"""
output = np.array([np.real(np.vdot(z, b) / np.vdot(b, b)) for b in self.sigma_R_basis])
return output
def round_coordinates(coordinates):
"""给出积分剩余部分"""
coordinates = coordinates - np.floor(coordinates)
return coordinates
def coordinate_wise_random_rounding(coordinates):
r = CKKSEncoder.round_coordinates(coordinates)
f = np.array([np.random.choice([c, c - 1], 1, p=[1 - c, c]) for c in r]).reshape(-1)
rounded_coordinates = coordinates - f
rounded_coordinates = [int(coeff) for coeff in rounded_coordinates]
return rounded_coordinates
def sigma_R_discretization(self, z):
"""使用坐标随机四舍五入将矢量投影到坐标上。"""
coordinates = self.compute_basis_coordinates(z)
rounded_coordinates = CKKSEncoder.coordinate_wise_random_rounding(coordinates)
y = np.matmul(self.sigma_R_basis.T, rounded_coordinates)
return y
使用尺度参数Δ来实现固定的精度水平
def encode(self, z: np.array) -> Polynomial:
pi_z = self.pi_inverse(z)
scaled_pi_z = self.scale * pi_z
rounded_scale_pi_zi = self.sigma_R_discretization(scaled_pi_z)
p = self.sigma_inverse(rounded_scale_pi_zi)
# We round it afterwards due to numerical imprecision
coef = np.round(np.real(p.coef)).astype(int)
p = Polynomial(coef)
return p
def decode(self, p: Polynomial) -> np.array:
rescaled_p = p / self.scale
z = self.sigma(rescaled_p)
pi_z = self.pi(z)
return pi_z
完整的代码 + 执行(想看代码直接跳)
import numpy as np
# 我们先设置一下参数
M = 8
scale = 64
from numpy.polynomial import Polynomial
class CKKSEncoder:
""" 基本CKKS编码器将编码变为多项式。"""
def __init__(self, M:int, scale:float):
"""初始化编码器M的一个2的幂。"""
"""xi,是单位的第m次根,将作为我们计算的基础。"""
self.xi = np.exp(2 *np.pi * 1j / M)
self.M = M
self.create_sigma_R_basis()
self.scale = scale
def pi(self, z: np.array) -> np.array:
"""将H的向量投影到C ^ {N / 2}中"""
N = self.M // 4
return z[:N]
def pi_inverse(self, z: np.array) -> np.array:
"""将向量C ^ {N / 2}展开复共轭。"""
z_conjugate = z[::-1]
z_conjugate = [np.conjugate(x) for x in z_conjugate]
return np.concatenate([z, z_conjugate])
@staticmethod
def vandermonde(xi: np.complex128, M: int) -> np.array:
"""从单位的m次根计算万德蒙矩阵。"""
N = M // 2
matrix = []
# 我们将生成矩阵的每一行
for i in range(N):
# 每一行我们选择一个不同的根
root = xi ** (2 * i + 1)
row = []
# 然后我们储存它的权值
for j in range(N):
row.append(root ** j)
matrix.append(row)
return matrix
def sigma(self, p: Polynomial) -> np.array:
"""通过将多项式应用于单位的m次根来解码多项式"""
outputs = []
N = self.M // 2
# 我们只需要把多项式应用到根上
for i in range(N):
root = self.xi ** (2 * i + 1)
output = p(root)
outputs.append(output)
return np.array(outputs)
def sigma_inverse(self, b: np.array) -> Polynomial:
"""用m次单位根将向量b编码成多项式。"""
# 首先我们先创建一个万德蒙矩阵
A = CKKSEncoder.vandermonde(self.xi, M)
# 然后我们解决这个方程
coeffs = np.linalg.solve(A, b)
# 最后我们输出这个多项式
p = Polynomial(coeffs)
return p
def create_sigma_R_basis(self):
self.sigma_R_basis = np.array(self.vandermonde(self.xi, self.M)).T
def compute_basis_coordinates(self, z):
"""计算向量相对于正交基的坐标。"""
output = np.array([np.real(np.vdot(z, b) / np.vdot(b, b)) for b in self.sigma_R_basis])
return output
def round_coordinates(coordinates):
"""给出积分剩余部分"""
coordinates = coordinates - np.floor(coordinates)
return coordinates
def coordinate_wise_random_rounding(coordinates):
r = CKKSEncoder.round_coordinates(coordinates)
f = np.array([np.random.choice([c, c - 1], 1, p=[1 - c, c]) for c in r]).reshape(-1)
rounded_coordinates = coordinates - f
rounded_coordinates = [int(coeff) for coeff in rounded_coordinates]
return rounded_coordinates
def sigma_R_discretization(self, z):
"""使用坐标随机四舍五入将矢量投影到坐标上。"""
coordinates = self.compute_basis_coordinates(z)
rounded_coordinates = CKKSEncoder.coordinate_wise_random_rounding(coordinates)
y = np.matmul(self.sigma_R_basis.T, rounded_coordinates)
return y
def encode(self, z: np.array) -> Polynomial:
pi_z = self.pi_inverse(z)
scaled_pi_z = self.scale * pi_z
rounded_scale_pi_zi = self.sigma_R_discretization(scaled_pi_z)
p = self.sigma_inverse(rounded_scale_pi_zi)
# We round it afterwards due to numerical imprecision
coef = np.round(np.real(p.coef)).astype(int)
p = Polynomial(coef)
return p
def decode(self, p: Polynomial) -> np.array:
rescaled_p = p / self.scale
z = self.sigma(rescaled_p)
pi_z = self.pi(z)
return pi_z
# 现在我们可以用添加的方法初始化编码器
encoder = CKKSEncoder(M, scale)
z = np.array([3 +4j, 2 - 1j])
print(z)
p = encoder.encode(z)
print(p)
q = encoder.decode(p)
print(q)
我们可以看到,它编码和解码都没有问题。
更多推荐
所有评论(0)