CKKS :Part1,普通編碼/解碼

這篇文章,翻譯於:【CKKS EXPLAINED: PART 1, VANILLA ENCODING AND DECODING

主要介紹爲CKKS中編碼/解碼做鋪墊,講一些基礎知識

介紹

同態加密是一個很有前途的領域,它允許對密文進行計算。下面這篇優秀的文章《什麼是同態加密》對同態加密是什麼以及這一研究領域的利害關係進行了廣泛的解釋。

在本系列文章中,我們將深入研究Cheon-Kim-Song(CKKS)方案,該方案首次在論文《Homomorphic Encryption for Arithmetic of Approximate Numbers》中討論。CKKS允許我們對複數向量(也就是實數)進行計算。我們的想法是,我們將用Python實現CKKS,然後通過使用這些加密原語,我們可以探索如何執行復雜的操作,如線性迴歸、神經網絡等。

上圖提供了CKKS的主要流程。我們可以看到,消息m是一個向量,我們希望對其執行某些計算,它首先被編碼爲明文多項式$p(X)$,然後使用公鑰進行加密。

CKKS使用多項式,因爲與向量計算相比,它們在安全性和效率之間提供了良好的折衷。【也就是使用RLWE和LWE之間區別,RLWE更加安全和高效

一旦消息m被加密爲c(一對多項式),CKKS就提供了幾個可以對其執行的操作,例如加法、乘法和旋轉。

如果我們用f表示函數,f是同態運算【加法和乘法】的組合,那麼用私鑰去解密 c'=f(c),然後解碼,我們將得到m=f(m)。

實現同態加密方案的核心思想是在編碼、解碼、加密和解密上具有同態屬性,這樣,對密文的操作將被正確地解密和解碼,並提供輸出,就像直接對明文進行操作一樣。

因此,在本文中,我們將看到如何實現編碼和解碼,在後面的文章中,我們將繼續實現加密和解密,以獲得同態加密方案。

預備知識

建議掌握線性代數和環理論的基本知識,以便更好地理解CKKS是如何實現的。您可以通過以下鏈接瞭解這些主題:

具體到本文中,我們將依賴以下概念:

如果你想運行該項目的代碼,你可以在這裏找到它。

CKKS編碼

CKKS利用整型多項式環的豐富結構實現其明文和密文空間。儘管如此,數據更多地以向量的形式出現,而不是以多項式的形式出現。

因此,我們需要將輸入的複數向量\(z\in \mbox{C}^{\frac{N}{2}}\)編碼成一個多項式\(m\left( X \right)=\frac{Z\left[ X \right]}{X^{N}+1}\)
用N表示多項式模的次數,其中N是二的次冪。把\(\Phi _{M}=X^{N}+1\)(其中M=2N)叫做M次分圓多項式。明文空間是多項式環\(R=\frac{Z\left[ X \right]}{X^{N}+1}\)。用\(\xi _{M}=e^{\frac{2i\pi }{M}}\)表示M次單位根

爲了理解如何將向量編碼爲多項式,以及對多項式執行計算是如何反映在向量上的,我們將首先用一個普通的示例進行實驗,我們將一個複數向量\(z\in \mbox{C}^N\)編碼爲一個複數多項式\(m\left( X \right)=\frac{C\left[ X \right]}{X^{N}+1}\),然後介紹CKKS編碼:將一個複數向量\(z\in \mbox{C}^{N/2}\)編碼爲一個整數多項式\(m\left( X \right)=\frac{Z\left[ X \right]}{X^{N}+1}\)

普通編碼

這裏我們將簡單介紹將一個複數向量\(z\in \mbox{C}^N\)編碼爲一個複數多項式\(m\left( X \right)=\frac{C\left[ X \right]}{X^{N}+1}\)的情況。

爲此,我們使用標準嵌入\(\sigma :\frac{\mbox{C}\left[ X \right]}{X^{N}+1}—>\mbox{C}^{N}\),對我們的向量進行解碼和編碼。
想法很簡單,將多項式\(m(X)\)解碼爲一個向量\(z\) ,即用分圓多項式\(\Phi _{M}=X^{N}+1\)的根\(\xi ,\xi ^{3},...,\xi ^{2N-1}\)去計算該多項式。

所以爲了解碼多項式\(m(X)\),我們定義\(\sigma \left( m \right)=m\left( m\left( \xi \right),m\left( \xi ^{3} \right),...,m\left( \xi ^{2N-1} \right) \right)\),這裏\(σ\)定義了一個同構,這意味着它是一個雙射同態,因此任何向量都將唯一地編碼到其相應的多項式中,反之亦然。【意思就是,只需把這些根帶入多項式,就能得到對應的向量值】

麻煩的是如何把向量\(z\in \mbox{C}^N\)編碼成多項式\(m\left( X \right)=\frac{C\left[ X \right]}{X^{N}+1}\),這意味着要求\(σ^{-1}\),因此問題就是要找到一個多項式\(m\left( X \right)=\sum_{}^{}{\alpha _{i}X^{i}\in \frac{\mbox{C}\left[ X \right]}{\left( X^{N}+1 \right)}}\),給出一個向量\(z\in \mbox{C}^{N}\),使得\(\sigma \left( m \right)=m\left( m\left( \xi \right),m\left( \xi ^{3} \right),...,m\left( \xi ^{2N-1} \right) \right)=(z_1,...,z_N)\)

進一步研究這個問題,我們最終得到了以下系統:\(\sum_{}^{}{\alpha _{j}\left( \zeta ^{2i-1} \right)^{j}=z_{i}},i=1,...,N\)

這可以看作是一個線性方程:\(\Alpha \alpha =z\),其中A是\(\left( \zeta ^{2i-1} \right)_{i=1,...,N}\)的範德蒙矩陣,\(σ\)是多項式的係數,z是我們編碼得到的向量。

所以我們有\(\alpha =\Alpha ^{-1}z\)\(\sigma ^{-1}\left( z \right)=\sum_{}^{}{\alpha _{i}X^{i}\in \frac{\mbox{C}\left[ X \right]}{X^{N}+1}}\)

舉例

現在讓我們來看一個例子,以便更好地理解我們到目前爲止討論的內容。
假定\(M=8,N=\frac{M}{2}=4,\Phi _{M}\left( X \right)=X^{4}+1,\omega =e^{\frac{2i\pi }{8}}=e^{\frac{i\pi }{4}}\)
我們的目標是對以下向量進行編碼:[1,2,3,4]和[−1,−2,−3,−4] ,對它們進行解碼,對它們的多項式進行加法和乘法,然後對其進行解碼。

正如我們所見,爲了解碼多項式,我們只需要根據M次單位根的冪來計算它。我們這裏選擇\(\xi _{M}=\omega =e^{\frac{i\pi \pi }{4}}\)
一旦我們有了\(\xi\)\(M\),我們就可以定義σ及其逆,分別進行解碼和編碼。

實現

1、現在我們使用Python實現普通的編碼和解碼:

import numpy as np

# First we set the parameters
M = 8
N = M //2

# We set xi, which will be used in our computations
xi = np.exp(2 * np.pi * 1j / M)
xi

輸出:(0.7071067811865476+0.7071067811865475j)

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)

2、讓我們先對一個向量進行編碼,看看它是如何使用實數編碼的。

# First we initialize our encoder
encoder = CKKSEncoder(M)

b = np.array([1, 2, 3, 4])
b

array([1, 2, 3, 4])
現在讓我們對向量進行編碼。

p = encoder.sigma_inverse(b)
p

輸出:\(x↦(2.5+4.440892098500626e^-16j)+((-4.996003610813204e^{-16}+0.7071067811865479j))x+((-3.4694469519536176e^{-16}+0.5000000000000003j))x^2+((-8.326672684688674e^{-16}+0.7071067811865472j))x^3\)
3、現在讓我們看看如何從多項式中提取我們最初得到的向量:

b_reconstructed = encoder.sigma(p)
b_reconstructed

輸出:\(array([1.-1.11022302e^{-16}j, 2.-4.71844785e^{-16}j, 3.+2.77555756e^{-17}j, 4.+2.22044605e^{-16}j])\)
我們可以看到重建值和初始向量非常接近。

np.linalg.norm(b_reconstructed - b)

輸出:\(6.944442800358888e-16\)
如前所述,不是隨機選擇σ來編碼和解碼的,但它有很多很好的特性。其中,σ是同構的,因此多項式的加法和乘法將導致編碼向量的係數的加法和乘法。
σ的同態性質是由於:\(X^{N}+1=0\; and\; \zeta ^{N}+1=0\)
4、我們現在可以開始對幾個向量進行編碼,看看如何對它們執行同態運算並對其進行解碼。

m1 = np.array([1, 2, 3, 4])
m2 = np.array([1, -2, 3, -4])

p1 = encoder.sigma_inverse(m1)
p2 = encoder.sigma_inverse(m2)

我們可以看到,加法非常簡單。

p_add = p1 + p2
p_add

輸出:\(x↦(2.0000000000000004+1.1102230246251565e^{-16}j)+((-0.7071067811865477+0.707106781186547j))x+((2.1094237467877966e^{-15}-1.9999999999999996j))x^2+((0.7071067811865466+0.707106781186549j))x^3\)
正如預期的那樣,我們看到p1+p2正確解碼爲[2,0,6,0]。

encoder.sigma(p_add)

輸出:\(array([2.0000000e+00+3.25176795e-17j, 4.4408921e-16-4.44089210e-16j, 6.0000000e+00+1.11022302e-16j, 4.4408921e-16+3.33066907e-16j])\)
5、因爲在進行乘法運算時,我們可能會得到階數大於N的項,我們需要使用\(X^N+1\)進行模運算。
要執行乘法,我們首先需要定義我們將使用的多項式模。

poly_modulo = Polynomial([1,0,0,0,1])
poly_modulo

輸出:\(x↦1.0+0.0x+0.0x^2+0.0x^3+1.0x^4\)
現在我們可以進行乘法運算了。

p_mult = p1 * p2 % poly_modulo

6、最後,如果我們解碼它,我們可以看到我們得到了預期的結果。

encoder.sigma(p_mult)

輸出:\(array([ 1.-8.67361738e-16j, -4.+6.86950496e-16j, 9.+6.86950496e-16j, -16.-9.08301212e-15j])\)
因此,我們可以看到,我們的簡單編碼和解碼正常,因爲它具有同態特性,並且是向量和多項式之間的一對一映射。

雖然這是一個很大的進步,但我們實際上撒謊了,因爲如果你之前注意到,當我們編碼時\(σ^{-1}\)多項式有復係數。因此,雖然編碼和解碼確實是同態的,而且是一對一的,但它們所涵蓋的領域是相同的\(C^N→ℂ[X] /(X^N+1)\)。因爲我們真的希望多項式屬於\(ℤ[X] /(X^N+1)\)爲了使用整數多項式環的所有屬性,我們需要確保編碼輸出具有整數係數而不是複數係數的多項式。

舉一個比較具體的例子:

所以我希望你們喜歡這篇關於將複數編碼成多項式進行同態加密的小介紹。我們將在下一篇文章中看到如何實現CKKS中使用的實際編碼和解碼,敬請期待!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章