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]

我希望你們喜歡這篇關於將復數向量編碼成多項式進行同態加密的小介紹。我們將在下面的文章中進一步深入探討這一點,敬請期待!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM