TFHE——基于[Discretized] Torus的全同态加密 代码解析
TFHE——基于[Discretized] Torus的全同态加密 代码解析
1. 引言
前序博客见:
Zama团队的Marc Joye 2021年论文 Guide to Fully Homomorphic Encryption over the [Discretized] Torus,开源代码实现见:
在该论文中,标记规则为:
- 现代架构中的位精度通常为32位或64位,本代码实现选择的是64位,即 q = 2 64 q=2^{64} q=264。
- 本代码实现中,取明文modulus p = 2 4 = 16 p=2^{4}=16 p=24=16,对应的取值范围为 [ − 8 , 7 ) [-8,7) [−8,7)或 0 ∽ 15 0\backsim 15 0∽15。
- LWE代码实现中取
n
=
630
n=630
n=630;GLWE代码实现中,取
N
=
1024
,
k
=
1
N=1024,k=1
N=1024,k=1。
- 由于 B ∈ { 0 , 1 } \mathbb{B}\in\{0,1\} B∈{0,1},实际TLWE加密密钥中 s i s_i si值要么为0,要么为1;实际TGLWE加密密钥中 s i \mathfrak{s}_i si值为多项式 B N [ X ] = B [ X ] / ( X N + 1 ) = { 0 , 1 } [ X ] / ( X N + 1 ) \mathbb{B}_N[X]=\mathbb{B}[X]/(X^N+1)=\{0, 1\}[X]/(X^N + 1) BN[X]=B[X]/(XN+1)={0,1}[X]/(XN+1)。
- TLWE实际代码中,取 ℓ = 4 , B = 2 4 = 16 , q = 2 64 \ell=4,B=2^4=16,q=2^{64} ℓ=4,B=24=16,q=264;TGGSW实际代码中,取 ℓ = 2 , B = 2 8 = 256 , q = 2 64 \ell=2,B=2^8=256,q=2^{64} ℓ=2,B=28=256,q=264。
具体各相关常量值定义为:
/// Decomposition basis for the external product. This value is used implicitely.
// pub const B: usize = 256;
/// Ciphertext modulus. This value is used implicitely.
// pub const Q: usize = 2^64;
/// Plaintext modulus
pub const P: usize = 16;
/// Number of decomposition layers for the external product.
pub const ELL: usize = 2;
/// GLWE dimension
#[allow(non_upper_case_globals)]
pub const k: usize = 1;
/// Degree `N` of irreducible polynomial X^N + 1
pub const N: usize = 1024;
/// Dimension of LWE ciphertexts
pub const LWE_DIM: usize = 630;
2. encode/decode
加密算法以(discretized)torus元素,或更准确来说,以 P \mathcal{P} P中元素为输入。Encoding/Decoding编解码的目的,是为了支持更多的输入形式。
令 M \mathcal{M} M为任意有限消息空间,其cardinality # M = p \#\mathcal{M}=p #M=p,且 p = 2 v p=2^v p=2v。明文空间 P = T p ⊂ T \mathcal{P}=\mathbb{T}_p\sub\mathbb{T} P=Tp⊂T,且 q = 2 Ω q=2^{\Omega} q=2Ω。
- 编码函数为: E n c o d e : M → P Encode:\mathcal{M}\rightarrow \mathcal{P} Encode:M→P,将消息 m ∈ M m\in\mathcal{M} m∈M映射为 μ ∈ P \mu\in\mathcal{P} μ∈P元素。编码用在加密之前。
- 解码函数为: D e c o d e : P → M Decode:\mathcal{P}\rightarrow \mathcal{M} Decode:P→M,将 μ ∈ P \mu\in\mathcal{P} μ∈P元素映射为 m ∈ M m\in\mathcal{M} m∈M消息。解码用于解密之后。
如若输入消息取值范围为 0 ∽ 15 0\backsim 15 0∽15,相应的编解码为:
// let msg = thread_rng().gen_range(0..16); // msg范围为0~15.
pub fn encode(msg: u8) -> u64 {
(msg as u64) << 60
}
//decode中有round操作,所以不是直接mu>>60,而是((mu >> 59) + 1) >> 1
pub fn decode(mu: u64) -> u8 {
((((mu >> 59) + 1) >> 1) % 16) as u8
}
注意,在代码实现中:
- TLWE和TGLWE均为对编码后的明文进行加密,解密后再解码才是原始明文。
- TGGSW为直接对明文进行加密,解密后的即为明文。无需编解码操作。
3. TLWE加解密及其密文运算
代码见lwe.rs
文件。
LWE代码实现中取
n
=
630
n=630
n=630。
3.1 TLWE加解密
注意,由于
B
∈
{
0
,
1
}
\mathbb{B}\in\{0,1\}
B∈{0,1},实际加密密钥中
s
i
s_i
si值要么为0,要么为1。
// 即经TLWE加密后的密文结构,为(a_1, ..., a_n, b)
pub struct LweCiphertext {
pub mask: Vec<u64>, //即为:(a_1, ..., a_n)
pub body: u64, //即为:b=\sum_{j=1}^n s_j\cdot a_j + \mu + e
}
// 为加密密钥(s_1, ..., s_n)
pub type LweSecretKey = Vec<u64>;
// 实际加密密钥中s_i值要么为0,要么为1。
pub fn lwe_keygen() -> LweSecretKey {
let mut sk = Vec::<u64>::with_capacity(LWE_DIM);
for _ in 0..LWE_DIM {
sk.push(thread_rng().gen_range(0..=1));
}
sk
}
TLWE加密为:【LWE_DIM,即为
n
n
n值,根据table 2,取
n
=
630
n=630
n=630。
μ
\mu
μ值为encode(msg)
】
pub fn encrypt(mu: u64, sk: &LweSecretKey) -> LweCiphertext {
let sigma = f64::powf(2.0, 49.0);
let normal = Normal::new(0.0, sigma).unwrap();
let e = normal.sample(&mut rand::thread_rng()).round() as i64; // small noise,为i64,有符号位整数
let mu_star = mu.wrapping_add_signed(e); // (mu + e) mod 2^{64}
//取随机值(a_1, ..., a_n)
let mask: Vec<u64> = (0..LWE_DIM).map(|_| rand::random::<u64>()).collect();
let mut body = 0u64;
for i in 0..LWE_DIM {
if sk[i] == 1 {
body = body.wrapping_add(mask[i]);
}
}
//即为:b=\sum_{j=1}^n s_j\cdot a_j + \mu + e
body = body.wrapping_add(mu_star);
LweCiphertext { mask, body }
}
TLWE解密为:【实际返回的是 μ ∗ \mu^* μ∗,而不是round后的 μ \mu μ值。】
pub fn decrypt(self, sk: &LweSecretKey) -> u64 {
let mut body: u64 = 0u64;
for i in 0..sk.len() { //计算\sum_{j=1}^{n}s_j \cdot a_j
if sk[i] == 1 {
body = body.wrapping_add(self.mask[i]);
}
}
// 计算 \mu^* = b-\sum_{j=1}^{n}s_j \cdot a_j
self.body.wrapping_sub(body) // mu_star
}
decrypt()
函数是
μ
∗
\mu^*
μ∗,需调用decode()
函数round后,才获得解密的msg值。
对应的测试用例为:
#[test]
fn test_keygen_enc_dec() {
let sk = lwe_keygen();
for _ in 0..100 {
let msg = thread_rng().gen_range(0..16);
let ct = LweCiphertext::encrypt(encode(msg), &sk);
let pt = decode(ct.decrypt(&sk));
assert_eq!(pt, msg);
}
}
3.2 TLWE密文加减法运算
pub fn add(self, rhs: Self) -> Self {
let mask = self
.mask
.iter()
.zip(rhs.mask)
.map(|(a, b)| a.wrapping_add(b))
.collect();
let body = self.body.wrapping_add(rhs.body);
LweCiphertext { mask, body }
}
pub fn sub(self, rhs: &Self) -> Self {
let mask = self
.mask
.iter()
.zip(&rhs.mask)
.map(|(a, b)| a.wrapping_sub(*b))
.collect();
let body = self.body.wrapping_sub(rhs.body);
LweCiphertext { mask, body }
}
测试用例为:
#[test]
fn test_add() {
let sk = lwe_keygen();
for _ in 0..100 {
let msg1 = thread_rng().gen_range(0..16);
let msg2 = thread_rng().gen_range(0..16);
let ct1 = LweCiphertext::encrypt(encode(msg1), &sk);
let ct2 = LweCiphertext::encrypt(encode(msg2), &sk);
let res = ct1.add(ct2);
let pt = decode(res.decrypt(&sk));
assert_eq!(pt, (msg1 + msg2) % 16);
}
}
#[test]
fn test_sub() {
let sk = lwe_keygen();
for _ in 0..100 {
let msg1 = thread_rng().gen_range(0..16);
let msg2 = thread_rng().gen_range(0..16);
let ct1 = LweCiphertext::encrypt(encode(msg1), &sk);
let ct2 = LweCiphertext::encrypt(encode(msg2), &sk);
let res = ct1.sub(&ct2);
let pt = decode(res.decrypt(&sk));
assert_eq!(pt, (msg1.wrapping_sub(msg2)) % 16);
}
}
3.3 TLWE密文与某已知常量值的乘法
pub fn multiply_constant_assign(&mut self, constant: u64) -> &mut Self {
self.mask = self.mask.iter().map(|a| a.wrapping_mul(constant)).collect();
self.body = self.body.wrapping_mul(constant);
self
}
3.4 TLWE密文之间的乘法运算
3.4.1 gadget decomposition
关键的“gadget decomposition”技术示例为:
不过TLWE实际代码中,取
ℓ
=
4
,
B
=
2
4
=
16
,
q
=
2
64
\ell=4,B=2^4=16,q=2^{64}
ℓ=4,B=24=16,q=264:【表示将(范围在
Z
2
64
Z_{2^{64}}
Z264内的)多项式系数的
log
(
B
)
∗
ℓ
=
4
∗
4
=
16
\log(B)*\ell=4*4=16
log(B)∗ℓ=4∗4=16个最高有效位(MSB),decompose为
ℓ
\ell
ℓ个整数
u
i
,
j
u_{i,j}
ui,j,
u
i
,
j
u_{i,j}
ui,j的取值范围为
[
−
⌊
B
/
2
⌋
,
⌈
B
/
2
⌉
)
=
[
−
8
,
8
)
=
[
−
8
,
7
)
[-\lfloor B/2\rfloor, \lceil B/2\rceil)=[-8,8)=[-8,7)
[−⌊B/2⌋,⌈B/2⌉)=[−8,8)=[−8,7)。】
/// Approximate decomposition with lg(B) = 4 and ell = 4.
/// Takes a polynomial coefficient in Z_{2^64} and decomposes its 16 MSBs in 4 integers in `[-8, 7] as u64`.
pub fn decomposition_4_4(val: u64) -> [u64; 4] { //输入val,对应为v_i
let mut ret = [0u64; 4]; //此处的4,与ell值对应。
let rounded_val = round_value(val); //对应为\bar{v}_i
let mut carry = 0u64;
for i in 0..4 { // 实际ret中计算的为$G^{-1}(v_i)$,即相应的u_{i,j}
let mut res = ((rounded_val >> (4 * i)) & 0x0F) + carry;
let carry_bit = res & 8;
res = res.wrapping_sub(carry_bit << 1);
ret[i] = res;
carry = carry_bit >> 3;
}
ret
}
注意:
v
ˉ
i
≡
∑
j
=
1
ℓ
u
i
,
j
B
ℓ
−
j
(
m
o
d
B
ℓ
)
\bar{v}_i\equiv \sum_{j=1}^{\ell}u_{i,j}B^{\ell-j}(\mod B^{\ell})
vˉi≡∑j=1ℓui,jBℓ−j(modBℓ),其中
u
i
,
j
∈
[
−
⌊
B
/
2
⌋
,
⌈
B
/
2
⌉
)
u_{i,j}\in[-\lfloor B/2\rfloor,\lceil B/2\rceil)
ui,j∈[−⌊B/2⌋,⌈B/2⌉)。
其中对
B
ℓ
B^{\ell}
Bℓ做模运算,代码中对应
ℓ
=
4
,
B
=
2
4
=
16
,
q
=
2
64
\ell=4,B=2^4=16,q=2^{64}
ℓ=4,B=24=16,q=264,对应的round函数计算为:
//实际即为对$B^{\ell}=16^4=2^{16}$做round计算
pub fn round_value(val: u64) -> u64 {
let mut rounded_val = val >> 47;
rounded_val += rounded_val & 1;
rounded_val >>= 1;
rounded_val
}
4. TGLWE加解密及其密文运算
代码见glwe.rs
文件。
GLWE代码实现中,取
N
=
1024
,
k
=
1
N=1024,k=1
N=1024,k=1。
4.1 多项式表示
多项式表示见poly.rs
。
TGLWE将TLWE加密扩展到
T
N
,
q
[
X
]
\mathbb{T}_{N,q}[X]
TN,q[X] torus多项式。其中
T
N
,
q
[
X
]
=
T
q
[
X
]
/
(
X
N
+
1
)
\mathbb{T}_{N,q}[X]=\mathbb{T}_q[X]/(X^N+1)
TN,q[X]=Tq[X]/(XN+1)多项式以ResiduePoly
表示为:
/// Represents an element of Z_{q}\[X\]/(X^N + 1) with implicit q = 2^64.
#[derive(Clone, Serialize, Deserialize)]
pub struct ResiduePoly {
pub coefs: Vec<u64>,
}
相应的多项式运算,对应为其系数的运算。
get_random
用于生成TGLWE加密算法中mask
a
i
\mathfrak{a}_i
ai需用到随机多项式:
/// Generates a residue polynomial with random coefficients in \[0..2^64)
pub fn get_random() -> Self {
let coefs = (0..N).map(|_| rand::random::<u64>()).collect();
Self { coefs }
}
get_random_bin
用于生成TGLWE加密算法中加密私钥
s
i
\mathfrak{s}_i
si为多项式
B
N
[
X
]
=
B
[
X
]
/
(
X
N
+
1
)
=
{
0
,
1
}
[
X
]
/
(
X
N
+
1
)
\mathbb{B}_N[X]=\mathbb{B}[X]/(X^N+1)=\{0, 1\}[X]/(X^N + 1)
BN[X]=B[X]/(XN+1)={0,1}[X]/(XN+1):
/// Generates a residue polynomial with random coefficients in \[0..1\]
pub fn get_random_bin() -> Self {
let coefs = (0..N).map(|_| thread_rng().gen_range(0..=1)).collect();
Self { coefs }
}
X
j
⋅
p
(
X
)
X^j\cdot \mathfrak{p}(X)
Xj⋅p(X)单项式乘法运算multiply_by_monomial
为:【有
X
N
+
i
≡
−
X
i
(
m
o
d
X
N
+
1
)
X^{N+i}\equiv -X^i(\mod X^N+1)
XN+i≡−Xi(modXN+1)和
X
2
N
+
i
≡
X
i
(
m
o
d
X
N
+
1
)
X^{2N+i}\equiv X^i(\mod X^N+1)
X2N+i≡Xi(modXN+1)】
/// Tests that the monomial multiplication is coherent with monomial multiplication.
fn test_monomial_mult() {
for _ in 0..1000 {
let mut monomial_coefs = vec![0u64; N];
let monomial_non_null_term = thread_rng().gen_range(0..2 * N);
if monomial_non_null_term < 1024 {
monomial_coefs[monomial_non_null_term] = 1;
} else {
monomial_coefs[monomial_non_null_term % 1024] = 1u64.wrapping_neg();
}
let monomial = ResiduePoly {
coefs: monomial_coefs,
};
let polynomial = ResiduePoly::get_random();
let res_mul = polynomial.mul(&monomial);
let res_monomial_mul = polynomial.multiply_by_monomial(monomial_non_null_term);
assert_eq!(res_mul.coefs, res_monomial_mul.coefs);
}
}
/// Multiplies the residue polynomial by X^{exponent} = X^{2N + exponent}.
/// `exponent` is assumed to be reduced modulo 2N.
pub fn multiply_by_monomial(&self, exponent: usize) -> Self {
let mut rotated_coefs = Vec::<u64>::with_capacity(N);
let reverse = exponent >= N;
let exponent = exponent % N;
for i in 0..N {
rotated_coefs.push({
if i < exponent {
if reverse {
self.coefs[i + N - exponent]
} else {
self.coefs[i + N - exponent].wrapping_neg()
}
} else if reverse {
self.coefs[i - exponent].wrapping_neg()
} else {
self.coefs[i - exponent]
}
})
}
ResiduePoly {
coefs: rotated_coefs,
}
}
// TODO: use FFT for better performances
pub fn mul(&self, rhs: &ResiduePoly) -> Self {
let mut coefs = Vec::<u64>::with_capacity(N);
for i in 0..N {
let mut coef = 0u64;
for j in 0..i + 1 {
coef = coef.wrapping_add(self.coefs[j].wrapping_mul(rhs.coefs[i - j]));
}
for j in i + 1..N {
coef = coef.wrapping_sub(self.coefs[j].wrapping_mul(rhs.coefs[N - j + i]));
}
coefs.push(coef);
}
ResiduePoly { coefs }
}
4.2 TGLWE加解密及加减法运算
实际TGLWE加密密钥中 s i \mathfrak{s}_i si值为多项式 B N [ X ] = B [ X ] / ( X N + 1 ) = { 0 , 1 } [ X ] / ( X N + 1 ) \mathbb{B}_N[X]=\mathbb{B}[X]/(X^N+1)=\{0, 1\}[X]/(X^N + 1) BN[X]=B[X]/(XN+1)={0,1}[X]/(XN+1):
/// Generates a residue polynomial with random coefficients in \[0..1\]
pub fn get_random_bin() -> Self {
let coefs = (0..N).map(|_| thread_rng().gen_range(0..=1)).collect();
Self { coefs }
}
pub struct GlweCiphertext { //密文
pub mask: Vec<ResiduePoly>, //为多项式列表
pub body: ResiduePoly, //为多项式
}
/// Set of `k` polynomials in {0, 1}\[X\]/(X^N + 1).
#[derive(Clone)]
pub struct SecretKey {
pub polys: Vec<ResiduePoly>, //加密密钥,为多项式列表
}
注意实际代码实现中,明文 μ \mu μ取的是u64整数,而不是多项式,对应解密时也是取的常量项值:
// 注意输入明文mu为u64整数
pub fn encrypt(mu: u64, sk: &SecretKey) -> GlweCiphertext {
let sigma = f64::powf(2.0, 39.0);
let normal = Normal::new(0.0, sigma).unwrap();
let e = normal.sample(&mut rand::thread_rng()).round() as i64;
let mu_star = mu.wrapping_add_signed(e);
let mask: Vec<ResiduePoly> = (0..k).map(|_| ResiduePoly::get_random()).collect();
let mut body = ResiduePoly::default();
for i in 0..k {
body.add_assign(&mask[i].mul(&sk.polys[i]));
}
body.add_constant_assign(mu_star as u64);
GlweCiphertext { mask, body }
}
pub fn decrypt(&self, sk: &SecretKey) -> u64 {
let mut body = ResiduePoly::default();
for i in 0..k {
body.add_assign(&self.mask[i].mul(&sk.polys[i]));
}
let mu_star = self.body.sub(&body);
mu_star.coefs[0] //解密时,取的是常量项值
}
同时TGLWE密文具有加法同态性:
#[test]
fn test_keygen_enc_dec() {
let sk = keygen();
for _ in 0..100 {
let msg = thread_rng().gen_range(0..16);
let ct = GlweCiphertext::encrypt(encode(msg), &sk);
let pt = decode(ct.decrypt(&sk));
assert_eq!(pt, msg);
}
}
#[test]
fn test_add() {
let sk = keygen();
for _ in 0..100 {
let msg1 = thread_rng().gen_range(0..16);
let msg2 = thread_rng().gen_range(0..16);
let ct1 = GlweCiphertext::encrypt(encode(msg1), &sk);
let ct2 = GlweCiphertext::encrypt(encode(msg2), &sk);
let res = ct1.add(&ct2);
let pt = decode(res.decrypt(&sk));
assert_eq!(pt, (msg1 + msg2) % 16);
}
}
#[test]
fn test_sub() {
let sk = keygen();
for _ in 0..100 {
let msg1 = thread_rng().gen_range(0..16);
let msg2 = thread_rng().gen_range(0..16);
let ct1 = GlweCiphertext::encrypt(encode(msg1), &sk);
let ct2 = GlweCiphertext::encrypt(encode(msg2), &sk);
let res = ct1.sub(&ct2);
let pt = decode(res.decrypt(&sk));
assert_eq!(pt, (msg1.wrapping_sub(msg2)) % 16);
}
}
5. TGGSW加解密及其密文运算
代码见ggsw.rs
文件。`
5.1 TGGSW加解密
TGGSW加密为:
TGGSW密文,有一组TGLWE密文组成:【共
(
k
+
1
)
ℓ
(k+1)\ell
(k+1)ℓ个TGLWE密文】
- 1) k ℓ k\ell kℓ个对应为mask。
- 2) ℓ \ell ℓ个对应为body。
- 3)最后一个TGLWE密文,对应为对
msg * q/B^l
的加密。 - 4)TGLWE密文,要远短于TGGSW密文。
pub struct GgswCiphertext {
z_m_gt: Vec<GlweCiphertext>, //共$(k+1)\ell$个
}
其中gadget matrix
G
∈
T
N
,
q
[
X
]
(
k
+
1
)
×
(
k
+
1
)
ℓ
\mathbf{G}\in\mathbb{T}_{N,q}[X]^{(k+1)\times (k+1)\ell}
G∈TN,q[X](k+1)×(k+1)ℓ 的转置矩阵
G
T
\mathbf{G}^T
GT为:【共
(
k
+
1
)
ℓ
(k+1)\ell
(k+1)ℓ行,
(
k
+
1
)
(k+1)
(k+1)列】
TGGSW实际代码中,取
ℓ
=
2
,
B
=
2
8
=
256
,
q
=
2
64
\ell=2,B=2^8=256,q=2^{64}
ℓ=2,B=28=256,q=264,并因代码中固定
k
=
1
k=1
k=1,从而TGGSW加密实现为:【不过需注意,此处的TGGSW加密是为后续的bootstrapping中的blind rotation服务,实际是额外再乘以了
q
q
q,对应的"gadget vector"
g
\mathbf{g}
g 不再是
(
1
/
B
,
⋯
,
1
/
B
ℓ
)
∈
T
q
l
(1/B,\cdots,1/B^{\ell})\in\mathbb{T}_q^l
(1/B,⋯,1/Bℓ)∈Tql,而是
(
q
/
B
,
⋯
,
q
/
B
ℓ
)
∈
T
q
l
(q/B,\cdots,q/B^{\ell})\in\mathbb{T}_q^l
(q/B,⋯,q/Bℓ)∈Tql。】
pub fn encrypt(msg: u8, sk: &SecretKey) -> Self {
// initialize Z
let mut z_m_gt: Vec<GlweCiphertext> = (0..(k + 1) * ELL)
.map(|_| GlweCiphertext::encrypt(0, sk))
.collect();
// m * g, g being [q/B, ..., q/B^l]
let mut mg = [0u64; ELL];
//此处处理不够灵活,相当于固定了k=1值。
mg[0] = (msg as u64) << 56; //注意此处msg为u8类型,取值范围为0~15
mg[1] = (msg as u64) << 48;
// add m * G^t to Z
for i in 0..z_m_gt.len() {
if i < k * ELL { //处理mask部分
for j in 0..z_m_gt[i].mask.len() {
z_m_gt[i].mask[j].add_constant_assign(mg[i % ELL]);
}
} else { //处理body部分
z_m_gt[i].body.add_constant_assign(mg[i % ELL]);
}
}
GgswCiphertext { z_m_gt }
}
TGGSW密文中的最后一个TGLWE密文,对应为对msg * q/B^l
的加密。所以,TGGSW的解密,可表示为对最后一个TGLWE的解密:
// The last `GlweCiphertext` of `z_m_gt` is an encryption of msg * q/B^l
pub fn decrypt(self, sk: &SecretKey) -> u8 {
((((&self.z_m_gt[self.z_m_gt.len() - 1].decrypt(sk) >> 47) + 1) >> 1) % 16) as u8
}
其中:
- 对解密结果
μ
∗
\mu^*
μ∗做
>> 47) + 1) >> 1) % 16) as u8
,表示的是 m s g = ⌊ μ ∗ ∗ B ℓ / q ⌉ m o d p msg=\lfloor \mu^**B^{\ell}/q\rceil\mod p msg=⌊μ∗∗Bℓ/q⌉modp。
5.3 TGGSW和TGLWE密文乘法运算
TGGSW和TGLWE密文乘法运算,对应为ggsw.rs
中的external_product
函数:
/// Performs a product (GGSW x GLWE) -> GLWE.
pub fn external_product(&self, ct: &GlweCiphertext) -> GlweCiphertext {
let g_inverse_ct = apply_g_inverse(ct);
let mut res = GlweCiphertext::default();
for i in 0..(k + 1) * ELL {
for j in 0..k {
res.mask[j].add_assign(&g_inverse_ct[i].mul(&self.z_m_gt[i].mask[j]));
}
res.body
.add_assign(&g_inverse_ct[i].mul(&self.z_m_gt[i].body));
}
res
}
此时的decomposition matrix G \mathbf{G} G的逆变换 G − 1 \mathbf{G}^{-1} G−1为:【TGGSW实际代码中,取 ℓ = 2 , B = 2 8 = 256 , q = 2 64 \ell=2,B=2^8=256,q=2^{64} ℓ=2,B=28=256,q=264,并因代码中固定 k = 1 k=1 k=1。】【表示将(范围在 Z 2 64 Z_{2^{64}} Z264内的)多项式系数的 log ( B ) ∗ ℓ = 8 ∗ 2 = 16 \log(B)*\ell=8*2=16 log(B)∗ℓ=8∗2=16个最高有效位(MSB),decompose为 ℓ = 2 \ell=2 ℓ=2个整数 u i , j u_{i,j} ui,j, u i , j u_{i,j} ui,j的取值范围为 [ − ⌊ B / 2 ⌋ , ⌈ B / 2 ⌉ ) = [ − 128 , 128 ) = [ − 128 , 127 ] [-\lfloor B/2\rfloor, \lceil B/2\rceil)=[-128,128)=[-128,127] [−⌊B/2⌋,⌈B/2⌉)=[−128,128)=[−128,127]。】
/// Approximate decomposition with lg(B) = 8 and ell = 2.
/// Takes a polynomial coefficient in Z_{2^64} and decomposes its 16 MSBs in two signed 8-bit integers.
pub fn decomposition_8_2(val: u64) -> (i8, i8) {
let rounded_val = round_value(val);
if rounded_val & 128 == 128 {
(rounded_val as i8, ((rounded_val >> 8) + 1) as i8)
} else {
(rounded_val as i8, (rounded_val >> 8) as i8)
}
}
对应的 G − 1 ( c 2 ) \mathbf{G}^{-1}(\mathbf{c}_2) G−1(c2)运算为:【其中 c 2 \mathbf{c}_2 c2为TGLWE密文。分别对mask和body进行decomposition。】
/// Decomposition of a GLWE ciphertext.
fn apply_g_inverse(ct: &GlweCiphertext) -> Vec<ResiduePoly> {
let mut res: [ResiduePoly; (k + 1) * ELL] = Default::default();
for i in 0..N {
// mask decomposition
for j in 0..k {
let (nu_2, nu_1) = decomposition_8_2(ct.mask[j].coefs[i]);
res[j * ELL].coefs[i] = nu_1 as u64;
res[j * ELL + 1].coefs[i] = nu_2 as u64;
}
// body decomposition
let (nu_2, nu_1) = decomposition_8_2(ct.body.coefs[i]);
res[(k + 1) * ELL - 2].coefs[i] = nu_1 as u64;
res[(k + 1) * ELL - 1].coefs[i] = nu_2 as u64;
}
res.to_vec()
}
5.4 CMux
TFHE中,TGGSW密文与TGLWE密文的external product运算,的主要应用场景为:
- “controlled” multiplexer,或简称为CMUX。
由于:
- TGLWE密文具有加法同态属性
- TGGSW密文与TGLWE密文具有乘法同态属性
相应的CMux的输入参数均为密文:
/// Ciphertext multiplexer. If `ctb` is an encryption of `1`, return `ct2`. Else, return `ct1`.
pub fn cmux(ctb: &GgswCiphertext, ct1: &GlweCiphertext, ct2: &GlweCiphertext) -> GlweCiphertext {
let mut res = ct2.sub(ct1);
res = ctb.external_product(&res);
res = res.add(ct1);
res
}
6. bootstrapping
6.1 bootstrapping key自举密钥
在代码实现中,
E
n
c
r
y
p
t
和
D
\mathfrak{E}ncrypt和\mathfrak{D}
Encrypt和D加解密算法对应为TLWE,而
E
n
c
r
y
p
t
和
D
e
c
r
y
p
t
Encrypt和Decrypt
Encrypt和Decrypt加解密算法对应为TGGSW——底层本质为TGLWE。因此,对应的自举密钥为,以TGGSW加密算法,逐个对TLWE的私钥
s
s
s进行加密。【TLWE私钥
s
s
s的长度为LWE_DIM(630),且每个
s
i
s_i
si值为
0
0
0或者
1
1
1。】
/// Encrypts the bits of `s` under `sk`
pub fn compute_bsk(s: &LweSecretKey, sk: &SecretKey) -> BootstrappingKey {
let bsk: Vec<GgswCiphertext> = (0..LWE_DIM)
.map(|i| GgswCiphertext::encrypt(s[i].try_into().unwrap(), sk))
.collect();
bsk
}
6.2 recode
recode的目的,是将TGLWE私钥,转换为TLWE私钥:
/// Converts a GLWE secret key into a LWE secret key.
// TODO: generalize for k > 1
pub fn recode(&self) -> LweSecretKey {
self.polys[0]
.coefs
.iter()
.copied()
.map(|coef| coef)
.collect()
}
6.3 blind rotate
其中,modswitch
函数,用于将TLWE密文中的各值由模
q
q
q调整为模
2
N
2N
2N:【此代码实现中,有
q
=
2
64
,
2
N
=
2
∗
1024
=
2
11
q=2^{64},2N=2*1024=2^{11}
q=264,2N=2∗1024=211。】
/// Switch from ciphertext modulus `2^64` to `2N` (implicit `N = 1024`).
pub fn modswitch(&self) -> Self {
let mask = self.mask.iter().map(|a| ((a >> 52) + 1) >> 1).collect();
let body = ((self.body >> 52) + 1) >> 1;
LweCiphertext { mask, body }
}
其中:
-
c
~
\tilde{\mathbf{c}}
c~即为
modswitch
之后的TLWE密文。 - c = ( 0 , ⋯ , 0 , v ) \mathfrak{c}=(0,\cdots,0,v) c=(0,⋯,0,v),为TGLWE密文。
/// Performs the blind rotation of `self`.
// `self` is assumed to be a trivial encryption
// `c` is a modswitched LWE ciphertext (modulus = 2N)
pub fn blind_rotate(&self, c: LweCiphertext, bsk: &BootstrappingKey) -> Self {
//self为TGLWE密文
let mut c_prime = self.clone();
//利用了$X^{2N-i}\equiv X^{-i} \mod (X^N+1)$
c_prime.rotate_trivial((2 * N as u64) - c.body);//计算c'=X^{-\tidle{b}}\cdot c
for i in 0..LWE_DIM { //LWE_DIM,为TLWE中的n值
c_prime = cmux(&bsk[i], &c_prime, &c_prime.rotate(c.mask[i]));
}
c_prime
}
/// Multiplies by the monomial `X^exponent` the body of `self`.
/// `self` is assumed to be a trivial encryption.
fn rotate_trivial(&mut self, exponent: u64) { //因TGLWE密文$\mathfrak{c}=(0,\cdots,0,v)$,所以仅需要关注body乘以单项式。
//不过TGLWE密文的body本身也是多项式
self.body = self.body.multiply_by_monomial(exponent as usize);
}
//考虑TGLWE密文中的mask为非零的情况。
/// Multiplies by the monomial `X^exponent` every component of `self`.
pub fn rotate(&self, exponent: u64) -> Self {
let mut res = Self::default();
for i in 0..k {
res.mask[i] = self.mask[i].multiply_by_monomial(exponent as usize);
}
res.body = self.body.multiply_by_monomial(exponent as usize);
res
}
6.4 sample extraction
经BlindRotate之后,可将明文 μ ∈ P \mu\in\mathcal{P} μ∈P的TLWE密文,转换为,常量项为 μ \mu μ的多项式明文 μ ( X ) : = X − μ ~ ∗ ⋅ v ∈ P N [ X ] \mu(X):=X^{-\tilde{\mu}^*}\cdot \mathfrak{v}\in\mathcal{P}_N[X] μ(X):=X−μ~∗⋅v∈PN[X] 的TGLWE密文。基于不同的key,通过 μ \mu μ的refreshed TLWE加密,可提取该常量项。该过程称为sample extraction。
需注意,尽管其用于常量项,该技术也可调整为用于提取
μ
\mu
μ的其它元素。
/// Converts a GLWE ciphertext into a LWE ciphertext of dimension `N`.
// TODO: generalize for k > 1
pub fn sample_extract(&self) -> LweCiphertext {
//self为TGLWE密文
let mut mask = [0u64; N];
mask[0] = self.mask[0].coefs[0];
for i in 1..N { //固定为k=1的情况
mask[i] = self.mask[0].coefs[N - i].wrapping_neg();
}
let body = self.body.coefs[0]; //仅提取常量项值
LweCiphertext {
mask: mask.to_vec(),
body,
}
}
6.5 key switching
/// This test fails from time to time.
/// It might be the case that B = 16, ell = 4 isn't suitable for keyswitching.
#[test]
fn test_keyswitching() {
let sk1 = lwe_keygen();
let sk2 = keygen();
let ksk = compute_ksk(&sk2.recode(), &sk1); // list of encryptions under `sk1` of the bits of `sk2`.
for _ in 0..100 {
let msg = thread_rng().gen_range(0..8);
let ct = GlweCiphertext::encrypt(encode(msg), &sk2).sample_extract();
let ks = ct.keyswitch(&mut ksk.clone());
let res = ks.decrypt(&sk1);
let pt = decode(res);
assert_eq!(msg, pt)
}
}
6.5.1 key switching密钥
key switching密钥中,
s
s
s和
s
i
′
s_i'
si′均为TLWE私钥:【注意TLWE实际代码中,取
ℓ
=
4
,
log
(
B
)
=
4
,
B
=
2
4
=
16
,
q
=
2
64
\ell=4,\log(B)=4, B=2^4=16,q=2^{64}
ℓ=4,log(B)=4,B=24=16,q=264(表示将(范围在
Z
2
64
Z_{2^{64}}
Z264内的)多项式系数的
log
(
B
)
∗
ℓ
=
4
∗
4
=
16
\log(B)*\ell=4*4=16
log(B)∗ℓ=4∗4=16个最高有效位(MSB),decompose为
ℓ
\ell
ℓ个整数
u
i
,
j
u_{i,j}
ui,j,
u
i
,
j
u_{i,j}
ui,j的取值范围为
[
−
⌊
B
/
2
⌋
,
⌈
B
/
2
⌉
)
=
[
−
8
,
8
)
=
[
−
8
,
7
)
[-\lfloor B/2\rfloor, \lceil B/2\rceil)=[-8,8)=[-8,7)
[−⌊B/2⌋,⌈B/2⌉)=[−8,8)=[−8,7)。)】
/// Encrypts `sk1` under `sk2`.
// TODO: generalize for k > 1
pub fn compute_ksk(sk1: &LweSecretKey, sk2: &LweSecretKey) -> KeySwitchingKey {
let mut ksk = Vec::<LweCiphertext>::with_capacity(4 * N);
for bit in sk1.iter().take(N) {
// 4 layers in the decomposition for the KSK
for j in 0..4 {
let mu = bit << (48 + (4 * j)); // lg(B) = 4
ksk.push(LweCiphertext::encrypt(mu, sk2));
}
}
ksk
}
6.5.2 key switch
其中TLWE中decomposition matrix
G
\mathbf{G}
G的逆变换
G
−
1
\mathbf{G}^{-1}
G−1对应decomposition_4_4
函数。【注意TLWE实际代码中,取
ℓ
=
4
,
log
(
B
)
=
4
,
B
=
2
4
=
16
,
q
=
2
64
\ell=4,\log(B)=4, B=2^4=16,q=2^{64}
ℓ=4,log(B)=4,B=24=16,q=264(表示将(范围在
Z
2
64
Z_{2^{64}}
Z264内的)多项式系数的
log
(
B
)
∗
ℓ
=
4
∗
4
=
16
\log(B)*\ell=4*4=16
log(B)∗ℓ=4∗4=16个最高有效位(MSB),decompose为
ℓ
\ell
ℓ个整数
u
i
,
j
u_{i,j}
ui,j,
u
i
,
j
u_{i,j}
ui,j的取值范围为
[
−
⌊
B
/
2
⌋
,
⌈
B
/
2
⌉
)
=
[
−
8
,
8
)
=
[
−
8
,
7
)
[-\lfloor B/2\rfloor, \lceil B/2\rceil)=[-8,8)=[-8,7)
[−⌊B/2⌋,⌈B/2⌉)=[−8,8)=[−8,7)。)】
/// Switch to the key encrypted by `ksk`.
/// This reduces the dimension of the ciphertext.
// TODO: generalize for k > 1
pub fn keyswitch(&self, ksk: &mut KeySwitchingKey) -> Self {
let mut keyswitched = LweCiphertext {
body: self.body,
..Default::default()
}; //这实际上是c'=(0, ..., 0, b)
for i in (0..4 * N).step_by(4) { //TLWE中取\ell为4,即每4个对应一个a_i
let decomp = decomposition_4_4(self.mask[i / 4]); //这实际即为g^{-1}(a_i)
keyswitched = keyswitched //计算c'=c'-d'
.sub(ksk[i].multiply_constant_assign(decomp[0]))
.sub(ksk[i + 1].multiply_constant_assign(decomp[1]))
.sub(ksk[i + 2].multiply_constant_assign(decomp[2]))
.sub(ksk[i + 3].multiply_constant_assign(decomp[3]));
}
keyswitched
}
6.6 bootstrapping
整个bootstrapping流程为:
目前实现还有问题,待改进:【有时成功有时失败,应该还是有bug,问题可能在keyswitch?】【为何此处范围限制为了[0,7],而不是[0,15]?】
#[test]
// #[ignore]
fn test_bootstrapping() {
let sk1 = lwe_keygen();
let sk2 = keygen();
let bsk = compute_bsk(&sk1, &sk2); // list of encryptions under `sk2` of the bits of `sk1`.
let ksk = compute_ksk(&sk2.recode(), &sk1); // list of encryptions under `sk1` of the bits of `sk2`.
let lut = GlweCiphertext::trivial_encrypt_lut_poly();
for _ in 0..1 {
let msg = thread_rng().gen_range(0..8); //为何此处范围限制为了[0,7],而不是[0,15]?
let c = LweCiphertext::encrypt(encode(msg), &sk1).modswitch(); // "noisy" ciphertext that will be bootstrapped
let blind_rotated_lut = lut.blind_rotate(c, &bsk); // should return a GLWE encryption of X^{- \tilde{\mu}^*} * v(X) which should be equal to a polynomial with constant term \mu.
let res = blind_rotated_lut //为TGLWE密文
.sample_extract() //为TLWE密文
.keyswitch(&mut ksk.clone()) //为TLWE密文
.decrypt(&sk1); //TLWE解密
let pt = decode_bootstrapped(res); //解码
assert_eq!(msg, pt)
}
}
其中:
trivial_encrypt_lut_poly
函数返回的为 c = ( 0 , ⋯ , 0 , v ) \mathfrak{c}=(0,\cdots,0,v) c=(0,⋯,0,v),其中 v : = v ( X ) = ∑ i = 0 N − 1 ⌊ p ∗ i / ( 2 N ) ⌉ m o d p p X i ∈ P N [ X ] ⊂ T N , q [ X ] v:=v(X)=\sum_{i=0}^{N-1}\frac{\lfloor p*i/(2N)\rceil\mod p}{p}X^i\in\mathcal{P}_N[X]\sub\mathbb{T}_{N,q}[X] v:=v(X)=∑i=0N−1p⌊p∗i/(2N)⌉modpXi∈PN[X]⊂TN,q[X]。
/// Trivially encrypts the LUT polynomial.
pub fn trivial_encrypt_lut_poly() -> Self { //此处self是指TGLWE密文
// TODO: use iterator
let mut lut_coefs = [0u64; N];
for i in 0..N {
lut_coefs[(i.wrapping_sub(64)) % N] = encode(((P * i) / (2 * N)).try_into().unwrap());
}
Self {
body: ResiduePoly {
coefs: lut_coefs.to_vec(),
},
..Default::default()
}
}
decode_bootstrapped
解码:
//decode中有round操作,所以不是直接mu>>60,而是((mu >> 59) + 1) >> 1
pub fn decode(mu: u64) -> u8 {
((((mu >> 59) + 1) >> 1) % 16) as u8
}
pub fn decode_bootstrapped(mu: u64) -> u8 {
if (mu >> 63) == 1 {
decode(!mu) % 8
} else {
decode(mu) % 8
}
}
参考资料
[1] Zama团队的Marc Joye 2021年论文 Guide to Fully Homomorphic Encryption over the [Discretized] Torus
FHE系列博客
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)