2021SC@SDUSC

目录

介绍

CKKS编码和解码

 编码:Message -> m(X)

解码:Message <- m(X)

相关代码执行(重要)

将输入的实数进行共轭处理

将万德蒙矩阵通过数组呈现出来

检查元素是否被编码为整数多项式

编码器和解码器相关代码

使用尺度参数Δ来实现固定的精度水平

完整的代码 + 执行(想看代码直接跳)


介绍

在上一篇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)

我们可以看到,它编码和解码都没有问题。

Logo

鸿蒙生态一站式服务平台。

更多推荐