CKKS Part2: CKKS的编码和解码

该文章翻译自CKKS EXPLAINED, PART 2: FULL ENCODING AND DECODING,主要介绍CKKS方案中是如何编码和解码的(如何将复数向量转成整数多项式,以及如何求逆运算)

介绍

在前一篇文章《CKKS:第1部分,普通编码和解码》中,我们了解到,要在CKKS方案中实现加密复数向量的计算,必须首先构建一个编码和解码,将复数向量转换为多项式。

这个编/解码步骤是必要的,因为加密、解密和其他机制在多项式环上工作。因此,有必要找到一种将复数向量转换成多项式的方法。

我们还了解到,通过使用标准嵌入\(σ\),即通过\(X^N+1\)的根计算多项式来解码多项式,实现\(ℂ^N\to ℂ[X] /(X^N+1)\)。然而,因为我们希望编码器输出的是多项式\(ℤ[X] /(X^N+1)\),为了利用多项式整数环的结构,我们需要修改前一个文章中的普通编码器,以便能够输出“右环的多项式”。(不太懂,应该是能够输出不带\(i\)的多项式)

因此,在本文中,我们将探讨如何实现原始论文《Homomorphic Encryption for Arithmetic of Approximate Numbers》中使用的编码和解码,这将是我们从头开始实现CKK的第一步。

CKKS编码

与前一篇文章的不同之处在于,编码多项式的明文空间现在是\(R=Z\left[ X \right]/X^{N}+1\)而不是\(\mbox{C}\left[ X \right]/X^{N}+1\),所以编码值多项式的系数必须是整数系数,然而当我们编码一个\(C^N\)上的向量时,我们已经了解到它的编码后的多项式的系数不一定是整数(有的是复数系数)。
为了解决这个问题,让我们来看看标准嵌入\(σ\)\(R\)上的图像。

因为在\(R\)上的多项式是整数系数,即实数系数(整数是实数,实数不是整数),我们在复数根上计算它们,其中一半是另一半的共轭项(参见上一章),我们有\(\sigma \left( R \right)\in H=z\in \mbox{C}^{N}:z_{j}=\neg z_{-j}\)
像上一章的M=8:

从上面的照片看,\(\omega ^{1}=-\omega ^{7}\; and\; \omega ^{3}=-\omega ^{5}\),一般来说,我们用\(X^N+1\)的根计算一个多项式,对于任何多项式\(m\left( x \right)\in R,m\left( \xi ^{j} \right)=-m\left( \xi ^{-j} \right)=m\left( -\xi ^{-j} \right)\),因此,\(σ(R)\)上的任何元素实际上是在一个大小为\(N/2\)的空间中,而不是\(N\)。因此,如果我们在CKKS中编码向量时使用大小为\(N/2\)的复数向量,我们需要通过复制其共轭根的来扩展出它的另一半。

这个操作需要将(\(H\)指的是\(C^N\)空间)\(ℍ\)投射到\(ℂ^{N/2}\),在CKKS论文中该操作称为\(π\)。请注意,这也定义了同构。
现在我们可以从\(z∈ℂ^{N/2}\)开始,用\(π^{−1}\)展开(注意π是\(N\to N/2\)\(π^{−1}\)\(N/2\to N\)),我们可以得到\(π^{−1}(z)∈ℍ\).

image
我们面临的一个问题是,我们不能直接使用\(σ: R=ℤ[X]/(X^N+1)→σ(R)⊆ℍ\)(整数多项式变复数向量),因为\(ℍ\)不一定在\(σ(R)\)中. \(σ\)确实定义了同构,但仅从\(R\)\(σ(R)\). 为了证明\(σ(R)\)不等于\(ℍ\), 你可以注意到\(R\)是可数的,因此\(σ(R)\) 也是,但是\(ℍ\)不是,因为它与\(ℂ\)同构。

这一段不太理解。

这个细节很重要,因为这意味着我们必须找到一种在\(σ(R)\)上的映射\(π^{−1}(z)\),为此,我们将使用一种称为“coordinate-wise random rounding, 坐标随机舍入”的技术,该技术在 A Toolkit for Ring-LWE Cryptography中定义。这种舍入技术允许将实数\(x\)舍入到\(⌊x⌋\)\(⌊x⌋+1\)(实数变整数),我们将不深入讨论这个算法的细节。

这里使用的单位根和普通编码中使用的不同。

想法很简单,有一个正交基\(ℤ:1,X,....,X^{N−1}\),假设\(σ\)是同构的,\(σ(R)\) 有一个正交基: \(β=(b1,b2,…,bN)=(σ(1),σ(X),...,σ(X^{N−1}))\). 因此,对于任何\(z∈ℍ\), 我们将简单地将其投射到β上:$$z=\sum_{i=1}^{N}{z_{i}b_{i},z_{i}=\frac{<z,b_{i}>}{\left| \left| b_{i} \right| \right|^{2}}}$$

因为基要么是正交的,要么不是正交的,所以\(z_{i} =\frac{<z,b_{i}>}{\left| \left| b_{i} \right| \right|^{2}}\), 请注意,我们在这里使用的是hermitian积(厄米乘积,内积):\(<x,y>=\sum_{i=1}^{N}{x_{i}\overline{y_i}}\), 厄米乘积给出了真正的输出,因为我们它是作用在\(ℍ\)上的元素, 你可以通过计算来证明,或者你可以在\(ℍ\)\(ℝ^N\)之间找到同构关系,所以在\(ℍ上\)的内积将是实际的输出。

最后,一旦我们有了\(z_i\),我们只需要使用“coordinate-wise random rounding, 坐标随机舍入”将它们随机舍入到更高或更低的最接近整数。这样我们就得到了一个多项式,它的基坐标为整数\((σ(1),σ(X),...,σ(X^N)−1) )\),因此该多项式将属于\(σ(R)\)

一旦我们有了映射关系\(σ(R)\), 我们可以用\(σ^{−1}\)的输出,这正是我们想要的!

最后一个细节:因为舍入可能会破坏一些重要的数字,我们实际上需要在编码中乘以\(Δ>0\),在解码中除以\(Δ\)以保持\(1/Δ\)的精度。
要了解其工作原理,请假设您想要将\(x=1.4\)四舍五入,但不想将其四舍五入到最接近的整数,而是要将其四舍五入到最接近的\(0.25\)的倍数,以保持一定的精度。然后,您需要设置刻度\(Δ=4\),其精度为\(1/Δ=0.25\)。的确,现在当我们\(\left\lfloor \Delta x \right\rfloor=\left\lfloor 4\cdot 1.4 \right\rfloor=\left\lfloor 5.6 \right\rfloor=6\)一旦我们将其除以相同的Δ,我们得到1.5,这实际上是x=1.4的最接近0.25的倍数。

所以最后的编码过程是:
1、以\(z∈ℂ^{N/2}\)为例
2、利用映射\(π^{-1}\)将其扩展到\(H\)
3、将其乘以\(Δ\)以保证精度
4、映射到\(\sigma(R)\)\(\left\lfloor \Delta \pi ^{-1}\left( z \right) \right\rfloor_{\sigma \left( R \right)}\in \sigma \left( R \right)\)
5、使用\(σ:m\left( x \right)=\sigma ^{-1}\left( \left\lfloor \Delta \pi ^{-1}\left( z \right) \right\rfloor_{\sigma \left( R \right)} \right)\in R\)对其进行编码

解码过程要简单得多,从多项式m(X)我们只得到\(z=π∘σ(Δ^{−1}.m)\),即代入单位根到\(m(x)\)

实现

现在我们终于看到了完整的CKKS编码和解码是如何工作的,让我们来实现它吧!我们将使用之前用于Vanilla编码器和解码器的代码。

强烈推荐源代码:这里

(1)这是上一篇介绍的普通编码和解密:

import numpy as np
from numpy.polynomial import Polynomial

class CKKSEncoder:
    """Basic CKKS encoder to encode complex vectors into polynomials."""
    
    def __init__(self, M: int):
        """Initialization of the encoder for M a power of 2. 
        
        xi, which is an M-th root of unity will, be used as a basis for our computations.
        """
        self.xi = np.exp(2 * np.pi * 1j / M)
        self.M = M
        
    @staticmethod
    def vandermonde(xi: np.complex128, M: int) -> np.array:
        """Computes the Vandermonde matrix from a m-th root of unity."""
        
        N = M //2
        matrix = []
        # We will generate each row of the matrix
        for i in range(N):
            # For each row we select a different root
            root = xi ** (2 * i + 1)
            row = []

            # Then we store its powers
            for j in range(N):
                row.append(root ** j)
            matrix.append(row)
        return matrix
    
    def sigma_inverse(self, b: np.array) -> Polynomial:
        """Encodes the vector b in a polynomial using an M-th root of unity."""

        # First we create the Vandermonde matrix
        A = CKKSEncoder.vandermonde(self.xi, M)

        # Then we solve the system
        coeffs = np.linalg.solve(A, b)

        # Finally we output the polynomial
        p = Polynomial(coeffs)
        return p

    def sigma(self, p: Polynomial) -> np.array:
        """Decodes a polynomial by applying it to the M-th roots of unity."""

        outputs = []
        N = self.M //2

        # We simply apply the polynomial on the roots
        for i in range(N):
            root = self.xi ** (2 * i + 1)
            output = p(root)
            outputs.append(output)
        return np.array(outputs)

在notebook 环境中,在上面类的基础上重构。我们不需要每次添加或更改方法时都重新定义类,而只需使用Fastai\(fastcore\)包中的\(patch_to\)。这使我们能够对已经定义的对象进行改造。使用\(patch_to\)很方便,您可以使用添加的方法在每个单元重新定义CKKSEncoder。
(2)引入包

!pip3 install fastcore

from fastcore.foundation import patch_to

image

(3)$\pi $映射((N->N/2))和其逆映射(N/2->N)

@patch_to(CKKSEncoder)
def pi(self, z: np.array) -> np.array:
    """Projects a vector of H into C^{N/2}."""
    
    N = self.M // 4
    return z[:N]

@patch_to(CKKSEncoder)
def pi_inverse(self, z: np.array) -> np.array:
    """Expands a vector of C^{N/2} by expanding it with its
    complex conjugate."""
    
    z_conjugate = z[::-1]
    z_conjugate = [np.conjugate(x) for x in z_conjugate]
    return np.concatenate([z, z_conjugate])

M = 8

# We can now initialize our encoder with the added methods
encoder = CKKSEncoder(M)

#测试
z = np.array([0,1])
encoder.pi_inverse(z)

输出:array([0, 1, 1, 0])
(4)生成单位根的次幂

@patch_to(CKKSEncoder)
def create_sigma_R_basis(self):
    """Creates the basis (sigma(1), sigma(X), ..., sigma(X** N-1))."""

    self.sigma_R_basis = np.array(self.vandermonde(self.xi, self.M)).T
    
@patch_to(CKKSEncoder)
def __init__(self, M):
    """Initialize with the basis"""
    self.xi = np.exp(2 * np.pi * 1j / M)
    self.M = M
    self.create_sigma_R_basis()
    
encoder = CKKSEncoder(M)
encoder.sigma_R_basis

输出:
\(array([[ 1.00000000e+00+0.j, 1.00000000e+00+0.j,1.00000000e+00+0.j, 1.00000000e+00+0.j],[ 7.07106781e-01+0.70710678j, -7.07106781e-01+0.70710678j, -7.07106781e-01-0.70710678j, 7.07106781e-01-0.70710678j],[ 2.22044605e-16+1.j, -4.44089210e-16-1.j, 1.11022302e-15+1.j, -1.38777878e-15-1.j], [-7.07106781e-01+0.70710678j, 7.07106781e-01+0.70710678j,7.07106781e-01-0.70710678j, -7.07106781e-01-0.70710678j]])\)

下面测试\(ℤ(σ(1)、σ(X)、σ(X2)、σ(X3))\)的元素是否被编码为整数多项式:

# Here we simply take a vector whose coordinates are (1,1,1,1) in the lattice basis
coordinates = [1,1,1,1]

b = np.matmul(encoder.sigma_R_basis.T, coordinates) #矩阵*向量=向量
b

输出:\(array([1.+2.41421356j, 1.+0.41421356j, 1.-0.41421356j, 1.-2.41421356j])\)

p = encoder.sigma_inverse(b)
p

输出:\(x↦(1+2.220446049250313e^{-16}j)+((1+0j))x+((0.9999999999999998+2.7755575615628716e^{-17}j))x^2+((1+2.220446049250313e^{-16}j))x^3\)
可以见还是复数多项式。
(5)重新定义
compute_basis_coordinates(self, z):计算\(z_{i} =\frac{<z,b_{i}>}{\left| \left| b_{i} \right| \right|^{2}}\)
round_coordinates(coordinates):计算出小数部分
coordinate_wise_random_rounding(coordinates):

@patch_to(CKKSEncoder)
def compute_basis_coordinates(self, z):
    """Computes the coordinates of a vector with respect to the orthogonal lattice basis."""
    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):
    """Gives the integral rest."""
    coordinates = coordinates - np.floor(coordinates)
    return coordinates

def coordinate_wise_random_rounding(coordinates):
    """Rounds coordinates randonmly."""
    r = 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

@patch_to(CKKSEncoder)
def sigma_R_discretization(self, z):
    """Projects a vector on the lattice using coordinate wise random rounding."""
    coordinates = self.compute_basis_coordinates(z) #计算z_i
    
    rounded_coordinates = coordinate_wise_random_rounding(coordinates) #舍入取整
    y = np.matmul(self.sigma_R_basis.T, rounded_coordinates) #范德蒙矩阵*向量
    return y

encoder = CKKSEncoder(M)

最后,因为在舍入步骤中可能会损失精度,所以我们使用刻度参数\(Δ\)来达到固定的精度水平。
(6)加入精度控制

@patch_to(CKKSEncoder)
def __init__(self, M:int, scale:float):
    """Initializes with scale."""
    self.xi = np.exp(2 * np.pi * 1j / M)
    self.M = M
    self.create_sigma_R_basis()
    self.scale = scale
    
@patch_to(CKKSEncoder)
def encode(self, z: np.array) -> Polynomial:
    """Encodes a vector by expanding it first to H,
    scale it, project it on the lattice of sigma(R), and performs
    sigma inverse.
    """
    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

@patch_to(CKKSEncoder)
def decode(self, p: Polynomial) -> np.array:
    """Decodes a polynomial by removing the scale, 
    evaluating on the roots, and project it on C^(N/2)"""
    rescaled_p = p / self.scale
    z = self.sigma(rescaled_p)
    pi_z = self.pi(z)
    return pi_z

scale = 64

encoder = CKKSEncoder(M, scale)

我们现在就得到CKKS使用的完整编码器

(7)引用fastcore的完整代码

#引入包
!pip3 install fastcore
import numpy as np
from numpy.polynomial import Polynomial
from fastcore.foundation import patch_to

#CKKSEncoder类
class CKKSEncoder:
    """Basic CKKS encoder to encode complex vectors into polynomials."""
    
    def __init__(self, M: int):
        """Initialization of the encoder for M a power of 2. 
        
        xi, which is an M-th root of unity will, be used as a basis for our computations.
        """
        self.xi = np.exp(2 * np.pi * 1j / M)
        self.M = M
        
    @staticmethod
    def vandermonde(xi: np.complex128, M: int) -> np.array:
        """Computes the Vandermonde matrix from a m-th root of unity."""
        
        N = M //2
        matrix = []
        # We will generate each row of the matrix
        for i in range(N):
            # For each row we select a different root
            root = xi ** (2 * i + 1)
            row = []

            # Then we store its powers
            for j in range(N):
                row.append(root ** j)
            matrix.append(row)
        return matrix
    
    def sigma_inverse(self, b: np.array) -> Polynomial:
        """Encodes the vector b in a polynomial using an M-th root of unity."""

        # First we create the Vandermonde matrix
        A = CKKSEncoder.vandermonde(self.xi, M)

        # Then we solve the system
        coeffs = np.linalg.solve(A, b)

        # Finally we output the polynomial
        p = Polynomial(coeffs)
        return p

    def sigma(self, p: Polynomial) -> np.array:
        """Decodes a polynomial by applying it to the M-th roots of unity."""

        outputs = []
        N = self.M //2

        # We simply apply the polynomial on the roots
        for i in range(N):
            root = self.xi ** (2 * i + 1)
            output = p(root)
            outputs.append(output)
        return np.array(outputs)

#\pi 映射和 \pi逆映射
@patch_to(CKKSEncoder)
def pi(self, z: np.array) -> np.array:
    """Projects a vector of H into C^{N/2}."""
    
    N = self.M // 4
    return z[:N]

@patch_to(CKKSEncoder)
def pi_inverse(self, z: np.array) -> np.array:
    """Expands a vector of C^{N/2} by expanding it with its
    complex conjugate."""
    
    z_conjugate = z[::-1]
    z_conjugate = [np.conjugate(x) for x in z_conjugate]
    return np.concatenate([z, z_conjugate])

#生成本原根
@patch_to(CKKSEncoder)
def create_sigma_R_basis(self):
    """Creates the basis (sigma(1), sigma(X), ..., sigma(X** N-1))."""

    self.sigma_R_basis = np.array(self.vandermonde(self.xi, self.M)).T

#
@patch_to(CKKSEncoder)
def compute_basis_coordinates(self, z):
    """Computes the coordinates of a vector with respect to the orthogonal lattice basis."""
    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):
    """Gives the integral rest."""
    coordinates = coordinates - np.floor(coordinates)
    return coordinates

def coordinate_wise_random_rounding(coordinates):
    """Rounds coordinates randonmly."""
    r = 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

@patch_to(CKKSEncoder)
def sigma_R_discretization(self, z):
    """Projects a vector on the lattice using coordinate wise random rounding."""
    coordinates = self.compute_basis_coordinates(z)
    print("coordinates",coordinates)
    rounded_coordinates = coordinate_wise_random_rounding(coordinates)
    print("rounded_coordinates",rounded_coordinates)
    y = np.matmul(self.sigma_R_basis.T, rounded_coordinates)
    return y

#编码和解码
@patch_to(CKKSEncoder)
def __init__(self, M:int, scale:float):
    """Initializes with scale."""
    self.xi = np.exp(2 * np.pi * 1j / M)
    self.M = M
    self.create_sigma_R_basis()
    self.scale = scale
    
@patch_to(CKKSEncoder)
def encode(self, z: np.array) -> Polynomial:
    """Encodes a vector by expanding it first to H,
    scale it, project it on the lattice of sigma(R), and performs
    sigma inverse.
    """
    pi_z = self.pi_inverse(z)
    print("pi_z:",pi_z)
    scaled_pi_z = self.scale * pi_z
    print("scaled_pi_z:",scaled_pi_z)
    rounded_scale_pi_zi = self.sigma_R_discretization(scaled_pi_z)
    print("rounded_scale_pi_zi:",rounded_scale_pi_zi)
    p = self.sigma_inverse(rounded_scale_pi_zi)
    print("p:",p)
    # We round it afterwards due to numerical imprecision
    coef = np.round(np.real(p.coef)).astype(int)
    print("coef:",coef)
    p = Polynomial(coef)
    print("p:",p)
    return p

@patch_to(CKKSEncoder)
def decode(self, p: Polynomial) -> np.array:
    """Decodes a polynomial by removing the scale, 
    evaluating on the roots, and project it on C^(N/2)"""
    rescaled_p = p / self.scale
    print("rescaled_p:",rescaled_p)
    z = self.sigma(rescaled_p)
    print("z:",z)
    pi_z = self.pi(z)
    return pi_z

scale = 64
M=8
encoder = CKKSEncoder(M, scale)

#测试
z = np.array([3 +4j, 2 - 1j])
print("明文:",z)
p = encoder.encode(z)
print("编码后:",p)
d = encoder.decode(p)
print("解码后:",d)

输出:
明文: [3.+4.j 2.-1.j]
pi_z: [3.+4.j 2.-1.j 2.+1.j 3.-4.j]
scaled_pi_z: [192.+256.j 128. -64.j 128. +64.j 192.-256.j]
coordinates: [160. 90.50966799 160. 45.254834 ]
rounded_coordinates: [160, 90, 160, 46]
rounded_scale_pi_zi: [191.11269837+256.16652224j 128.88730163 -63.83347776j
128.88730163 +63.83347776j 191.11269837-256.16652224j]
p: (160-2.842170943040401e-14j) + (90.00000000000001+0j)·x¹ +(160+8.881784197001252e-16j)·x² +
(46.000000000000014+1.0658141036401503e-14j)·x³
coef: [160 90 160 46]
p: 160.0 + 90.0·x¹ + 160.0·x² + 46.0·x³
编码后: 160.0 + 90.0·x¹ + 160.0·x² + 46.0·x³
rescaled_p: 2.5 + 1.40625·x¹ + 2.5·x² + 0.71875·x³
z: [2.98613591+4.00260191j 2.01386409-0.99739809j 2.01386409+0.99739809j
2.98613591-4.00260191j]
解码后: [2.98613591+4.00260191j 2.01386409-0.99739809j]

这里可以看到:在sigma_R_discretization中就求出了整数多项式的系数

image

(8)不引用的完整代码

from numpy.polynomial import Polynomial
import numpy as np

def round_coordinates(coordinates):
    """Gives the integral rest."""
    coordinates = coordinates - np.floor(coordinates)
    return coordinates

def coordinate_wise_random_rounding(coordinates):
    """Rounds coordinates randonmly."""
    r = 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

class CKKSEncoder:
    """Basic CKKS encoder to encode complex vectors into polynomials."""
    
    def __init__(self, M:int, scale:float):
        """Initializes with scale."""
        self.xi = np.exp(2 * np.pi * 1j / M)
        self.M = M
        self.create_sigma_R_basis()
        self.scale = scale
        
    @staticmethod
    def vandermonde(xi: np.complex128, M: int) -> np.array:
        """Computes the Vandermonde matrix from a m-th root of unity."""
        
        N = M //2
        matrix = []
        # We will generate each row of the matrix
        for i in range(N):
            # For each row we select a different root
            root = xi ** (2 * i + 1)
            row = []

            # Then we store its powers
            for j in range(N):
                row.append(root ** j)
            matrix.append(row)
        return matrix
    
    def sigma_inverse(self, b: np.array) -> Polynomial:
        """Encodes the vector b in a polynomial using an M-th root of unity."""

        # First we create the Vandermonde matrix
        A = CKKSEncoder.vandermonde(self.xi, M)

        # Then we solve the system
        coeffs = np.linalg.solve(A, b)

        # Finally we output the polynomial
        p = Polynomial(coeffs)
        return p

    def sigma(self, p: Polynomial) -> np.array:
        """Decodes a polynomial by applying it to the M-th roots of unity."""

        outputs = []
        N = self.M //2

        # We simply apply the polynomial on the roots
        for i in range(N):
            root = self.xi ** (2 * i + 1)
            output = p(root)
            outputs.append(output)
        return np.array(outputs)
    

    def pi(self, z: np.array) -> np.array:
        """Projects a vector of H into C^{N/2}."""

        N = self.M // 4
        return z[:N]


    def pi_inverse(self, z: np.array) -> np.array:
        """Expands a vector of C^{N/2} by expanding it with its
        complex conjugate."""

        z_conjugate = z[::-1]
        z_conjugate = [np.conjugate(x) for x in z_conjugate]
        return np.concatenate([z, z_conjugate])
    
    def create_sigma_R_basis(self):
        """Creates the basis (sigma(1), sigma(X), ..., sigma(X** N-1))."""

        self.sigma_R_basis = np.array(self.vandermonde(self.xi, self.M)).T
    

    def compute_basis_coordinates(self, z):
        """Computes the coordinates of a vector with respect to the orthogonal lattice basis."""
        output = np.array([np.real(np.vdot(z, b) / np.vdot(b,b)) for b in self.sigma_R_basis])
        return output

    def sigma_R_discretization(self, z):
        """Projects a vector on the lattice using coordinate wise random rounding."""
        coordinates = self.compute_basis_coordinates(z)

        rounded_coordinates = coordinate_wise_random_rounding(coordinates)
        y = np.matmul(self.sigma_R_basis.T, rounded_coordinates)
        return y


    def encode(self, z: np.array) -> Polynomial:
        """Encodes a vector by expanding it first to H,
        scale it, project it on the lattice of sigma(R), and performs
        sigma inverse.
        """
        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:
        """Decodes a polynomial by removing the scale, 
        evaluating on the roots, and project it on C^(N/2)"""
        rescaled_p = p / self.scale
        z = self.sigma(rescaled_p)
        pi_z = self.pi(z)
        return pi_z

#测试
z = np.array([3 +4j, 2 - 1j])
print("明文:",z)
p = encoder.encode(z)
print("编码后:",p)
d = encoder.decode(p)
print("解密后:",d)

输出:
明文: [3.+4.j 2.-1.j]
pi_z: [3.+4.j 2.-1.j 2.+1.j 3.-4.j]
scaled_pi_z: [192.+256.j 128. -64.j 128. +64.j 192.-256.j]
coordinates [160. 90.50966799 160. 45.254834 ]
rounded_coordinates [160, 91, 160, 46]
rounded_scale_pi_zi: [191.81980515+256.87362902j 128.18019485 -63.12637098j
128.18019485 +63.12637098j 191.81980515-256.87362902j]
p: (160-1.4210854715202004e-14j) + (91-7.105427357601002e-15j)·x¹ +
(160+7.993605777301127e-15j)·x² +
(46.00000000000001+1.4210854715202004e-14j)·x³
coef: [160 91 160 46]
p: 160.0 + 91.0·x¹ + 160.0·x² + 46.0·x³
编码后: 160.0 + 91.0·x¹ + 160.0·x² + 46.0·x³
rescaled_p: 2.5 + 1.421875·x¹ + 2.5·x² + 0.71875·x³
z: [2.99718446+4.01365045j 2.00281554-0.98634955j 2.00281554+0.98634955j
2.99718446-4.01365045j]
解密后: [2.99718446+4.01365045j 2.00281554-0.98634955j]

我希望你们喜欢这篇关于将复数向量编码成多项式进行同态加密的小介绍。我们将在下面的文章中进一步深入探讨这一点,敬请期待!

posted @ 2022-02-05 12:33  PamShao  阅读(4443)  评论(13编辑  收藏  举报