# CKKS Part2: CKKS的编码和解码

## CKKS编码

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$$对其进行编码

## 实现

（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)


（2）引入包

!pip3 install fastcore

from fastcore.foundation import patch_to


（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)


（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]])$$

# 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


p = encoder.sigma_inverse(b)
p


（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)


（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)


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³

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]

（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)


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³

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]

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