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

介绍

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

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

我们还了解到,通过使用标准嵌入σ,即通过在\(X^N+1\)的根上计算多项式来解码多项式,我们能够在\(ℂ^N —>ℂ[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的复数向量,我们需要通过复制其共轭根的来扩展它的另一半。

这个操作需要将\(ℍ\)投射到\(ℂ^{N/2}\),在CKKS论文中该操作称为π。请注意,这也定义了同构。

现在我们可以从\(z∈ℂ^{N/2}\)开始,用\(π^{−1}\)展开(注意π是映射,\(π^{−1}\)是扩展),我们可以得到\(π^{−1}(z)∈ℍ\).

我们面临的一个问题是,我们不能直接使用\(σ: 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}\left( -y_{i} \right)}\), 厄米乘积给出了真正的输出,因为我们它是在ℍ上, 你可以通过计算来证明,或者注意到,你可以在ℍ 和\(ℝ^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。

所以最后的编码过程是:

以\(z∈ℂ^{N/2}\)为例

将其扩展到\(π^{-1}∈H\);

将其乘以Δ以保证精度

映射:\(\left\lfloor \Delta \pi ^{-1}\left( z \right) \right\rfloor_{\sigma \left( R \right)}\in \sigma \left( R \right)\)

使用σ:\(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)\)。

实现

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

在本文的其余部分中,让我们重构并构建我们在上一篇文章中创建的CKKSEncoder类。在笔记本电脑环境中,我们不需要每次添加或更改方法时都重新定义类,而只需使用Fastai的fastcore包中的patch_to。这使我们能够对已经定义的对象进行修补。使用patch_to纯粹是为了方便,您可以使用添加的方法在每个单元重新定义CKKSEncoder。

# !pip3 install fastcore

from fastcore.foundation import patch_to
@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]) # 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])

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

我们现在可以看看基:\(\sigma \left( 1 \right),\sigma \left( X \right),\sigma \left( X^{2} \right),\sigma \left( X^{3} \right)\)

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-16j)+((1+0j))x+((0.9999999999999998+2.7755575615628716e-17j))x^2+((1+2.220446049250313e-16j))x^3\)

@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) rounded_coordinates = coordinate_wise_random_rounding(coordinates)
y = np.matmul(self.sigma_R_basis.T, rounded_coordinates)
return y encoder = CKKSEncoder(M)

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

@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使用的完整编码器:

z = np.array([3 +4j, 2 - 1j])
z

输出:array([3.+4.j, 2.-1.j])

现在我们有一个整数多项式作为我们的编码。

p = encoder.encode(z)
p

\(x↦160.0+90.0x+160.0x^2+45.0x^3\)

而且它实际上解码得很好!

encoder.decode(p)
array([2.99718446+3.99155337j, 2.00281554-1.00844663j])

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

CKKS Part2: CKKS的编码和解码的更多相关文章

  1. CKKS Part3: CKKS的加密和解密

    本篇文章翻译于CKKS EXPLAINED, PART 3: ENCRYPTION AND DECRYPTION,主要介绍CKKS方案的加密和解密. 介绍 在上一篇 CKKS Part2: CKKS的 ...

  2. CKKS Part5: CKKS的重缩放

    本文翻译于 CKKS EXPLAINED, PART 5: RESCALING,主要介绍CKKS方案中最重要的技术- rescaling,重缩放技术 介绍 在CKKS的前一篇文章 CKKS Part4 ...

  3. java编码原理,java编码和解码问题

    java的编码方式原理 java的JVM的缺省编码方式由系统的“本地语言环境”设置确定,和操作系统的类型无关 . 在JAVA源文件-->JAVAC-->Class-->Java--& ...

  4. IO(六)--- 编码和解码

    编码: 把看得懂的字符变成看不懂码值这个过程我们称作为编码. 解码: 把码值查找对应的字符,我们把这个过程称作为解码. 注意: 以后编码与解码一般我们都使用统一的码表.否则非常容易出乱码. 常用码表: ...

  5. RapidJSON 代码剖析(三):Unicode 的编码与解码

    根据 RFC-7159: 8.1 Character Encoding JSON text SHALL be encoded in UTF-8, UTF-16, or UTF-32. The defa ...

  6. BASE64编码和解码(VC源代码) 并 内存加载 CImage 图像

      BASE64可以用来将binary的字节序列数据编码成ASCII字符序列构成的文本.完整的BASE64定义可见 RFC1421和 RFC2045.编码后的数据比原始数据略长,为原来的4/3.在电子 ...

  7. Android 中的编码与解码

    前言:今天遇到一个问题,一个用户在登录的时候,出现登录失败.但是其他用户登录都是正常的,经过调试发现登录失败的用户的密码中有两个特殊字符: * .#  . 特殊符号在提交表单的时候,出现了编码不一样的 ...

  8. base64编码、解码的C语言实现

    转自:http://www.cnblogs.com/yejianfei/archive/2013/04/06/3002838.html base64是一种基于64个可打印字符来表示二进制数据的表示方法 ...

  9. android Java BASE64编码和解码二:图片的编码和解码

    1.准备工作 (1)在项目中集成 Base64 代码,集成方法见第一篇博文:android Java BASE64编码和解码一:基础 (2)添加 ImgHelper 工具类 package com.a ...

随机推荐

  1. 【剑指Offer】二叉搜索树的后序遍历序列 解题报告(Python)

    [剑指Offer]二叉搜索树的后序遍历序列 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-intervi ...

  2. 大数据分布式存储之Cassandra

    分布式存储区别于集中式数据库存储,通过网络将海量数据存储到企业的各个数据节点(可能分布到不同的数据中心或机架上): 分布式存储需要考虑的问题 元数据管理 元数据是指数据本身的标识,通过元数据能很快的找 ...

  3. 从JVM设计角度解读Java内存模型

    第十六章:Java内存模型 本文我们将重点放在Java内存模型(JMM)的一些高层设计问题,以及JMM的底层需求和所提供的保证,还有一些高层设计原则背后的原理. 例如安全发布,同步策略的规范以及一致性 ...

  4. vue是如何通过diff算法做渲染更新

    渲染页面 图中框起来的部分,vue会根据响应式变化的通知生成一颗新的 Virtual Dom Tree,然后将新的Virtual Dom Tree 和之前的 Virtual Dom Tree 做 di ...

  5. springboot配置health接口

    springboot配置health接口 spring-boot-starter-actuator 健康监控配置及使用 这样是可以看到一些结果的 如果在配置文件中用了下面这个,也是可以生效的 # 不进 ...

  6. Kylin启动步骤

    Kylin本身的启动命令比较简单, 但是由于Kylin依赖的其他组件比较多, 所以把完整的启动步骤整理一下. 1.确保集群时间同步 首先查看集群中的时间是否同步 date 如果时间不一致,需要使用如下 ...

  7. 初识python 之 爬虫:BeautifulSoup 的 find、find_all、select 方法

    from bs4 import BeautifulSoup lxml 以lxml形式解析html,例:BeautifulSoup(html,'lxml') #  注:html5lib 容错率最高fin ...

  8. python_三元运算

    条件三元运算 # 三元条件运算,如果条件为真则返回x,如果条件为假则返回y x = 3 y = 5 ret = x if x > y else y print(ret) # 返回 y值 for循 ...

  9. Webstorm安装与配置

    一 下载 链接:https://pan.baidu.com/s/1gKxzGWvnoCpXPoe8zzfLnQ 提取码:5lyf 二 安装 https://www.jb51.net/softs/598 ...

  10. nuxt中iview按需加载配置

    在plugins/iview.js中修改 初始代码如下 import Vue from 'vue' import iView from 'iview' import locale from 'ivie ...