2021SC@SDUSC PALISADE开源库(二)CKKS讲解系列(二)完整编码和解码

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)

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

### 关于 CKKS 同态加密的使用文档 CKKS(Cheon-Kim-Kim-Song)是一种近似同态加密方案,允许对密文执行加法乘法操作而无需解密。这种技术广泛应用于隐私保护计算领域,特别是在机器学习模型训练推理过程中。 以下是关于 CKKS 的一些关键信息: #### 1. 安装 CKKS 大多数实现 CKKS 的开源项目基于 C++Python 缠绕接口开发。以下是一些常见的安装方法: - **Microsoft SEAL**: Microsoft 提供了一个流行的同态加密,支持 CKKS 方案[^3]。 ```bash git clone https://github.com/microsoft/SEAL.git cd SEAL && cmake . && make ``` - **HElib**: 另一个常用的同态加密也实现了部分 CKKS 功能[^4]。 ```bash git clone https://github.com/homenc/HElib.git cd HElib && ./bootstrap.sh && ./configure && make ``` #### 2. 使用示例 下面是一个简单的 Python 示例,展示如何利用 Microsoft SEAL 实现基本的 CKKS 加密运算[^5]: ```python from seal import * parms = EncryptionParameters(scheme_type.CKKS) poly_modulus_degree = 8192 coeff_mod_bit_sizes = [40, 20, 40] parms.set_poly_modulus_degree(poly_modulus_degree) parms.set_coeff_modulus(CoeffModulus.Create( poly_modulus_degree, coeff_mod_bit_sizes)) context = SEALContext(parms) print(f"Set encryption parameters and print them:") print_parameters(context) keygen = KeyGenerator(context) public_key = keygen.public_key() secret_key = keygen.secret_key() encryptor = Encryptor(context, public_key) evaluator = Evaluator(context) decryptor = Decryptor(context, secret_key) encoder = CKKSEncoder(context) scale = pow(2.0, 40) slots = encoder.slot_count() plain = Plaintext() encrypted_result = Ciphertext() vector_of_floats = [0, 1, 2, 3, 4, 5, 6, 7] encoder.encode(vector_of_floats, scale, plain) encryptor.encrypt(plain, encrypted_result) output = DoubleVector() decoder.decode(decryptor.decrypt(encrypted_result), output) print("Decrypted plaintext:", list(output)) ``` #### 3. 文档资源 官方文档通常是最权威的学习材料之一。对于 CKKS 的具体用法,可以参考以下链接: - **Microsoft SEAL Documentation**: 提供详细的教程技术细节[^6]。 - **PALISADE Homomorphic Encryption Library**: 这也是一个强大的工具包,提供了丰富的 API 示例代码[^7]。 #### 4. 性能优化技巧 为了提高 CKKS 密码系统的性能,开发者需要注意以下几个方面: - 调整多项式的模数阶次 `poly_modulus_degree` 来平衡安全性效率[^8]。 - 控制系数模数大小以减少噪声增长速度[^9]。 --- ###
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值