前言
在以往的研究中,我通常使用 SEAL 库进行同态加密编码。为了利用 CUDA 加速同态计算过程,本文选择在 WSL2 环境下安装并配置 PhantomFHE。
文章目录
- 前言
- 一、PhantomFHE
-
- [1. 基本依赖](#1. 基本依赖)
- [2. 下载 PhantomFHE 库](#2. 下载 PhantomFHE 库)
- [3. 链接 PhantomFHE 库](#3. 链接 PhantomFHE 库)
- [二、基于 PhantomFHE 的多项式运算实现](#二、基于 PhantomFHE 的多项式运算实现)
-
- [1. 导入依赖](#1. 导入依赖)
- [2. 参数打印函数](#2. 参数打印函数)
- [3. 编写代码](#3. 编写代码)
- [4. 代码执行](#4. 代码执行)
- Note:加密解密
一、PhantomFHE
PhantomFHE 库的源代码位于 https://github.com/encryptorion-lab/phantom-fhe。但是,如果想把 PhantomFHE 作为插件集成到自己的项目中,可以参考 https://github.com/FLL-Lab/Euston 中的实现方式。下面具体介绍如何以插件的方式使用 PhantomFHE。
1. 基本依赖
bash
sudo apt-get install clang
sudo apt-get install cmake
sudo apt-get install gcc
sudo apt-get install g++
sudo apt-get install git
2. 下载 PhantomFHE 库
- 下载并解压 Euston 压缩包,网址:https://github.com/FLL-Lab/Euston 。其中,将文件目录中
Euston/Euston-GPU/thirdparty这个文件夹整个复制到自己的项目下。比如,我直接将其复制到我的 LearnPhantom 项目文件夹下:

3. 链接 PhantomFHE 库
配置本项目的 CMakeLists.txt 文件。这里注意,Euston 项目已经为我们提供了 PhantomFHE 作为插件的 CMakeLists.txt 配置文件,位于 thirdparty/phantom-fhe/CMakeLists.txt,不要 修改此文件。以我的项目为例,只需在自己的项目主 CMakeLists.txt 文件(例如 LearnPhantom/CMakeLists.txt)中链接 PhantomFHE 库即可:
- 指定CMake版本
cpp
cmake_minimum_required(VERSION 3.28)
- 定义项目名称和使用语言:
cpp
project(LearnPhantom LANGUAGES CXX CUDA)
- 设置 C++ 和 CUDA 标准。这里我使用的标准是 C++17,且要求项目必须使用:
cpp
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
- 配置 CUDA:
cpp
#允许 CUDA 代码被拆分到多个 .cu 文件中分别编译,然后再链接
set(CMAKE_CUDA_SEPARABLE_COMPILATION ON)
# 让 CMake 自动检测当前机器的 NVIDIA GPU 架构,并为对应架构生成 CUDA 代码
set(CMAKE_CUDA_ARCHITECTURES native)
- 链接 PhantomFHE 库:
cpp
# 设置 PhantomFHE 根目录变量
set(PHANTOM_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/phantom-fhe)
# 全局加入 PhantomFHE 的头文件目录
include_directories(
${PHANTOM_ROOT}/include
)
#把 PhantomFHE 作为子项目加入
add_subdirectory(${PHANTOM_ROOT})
add_executable(LearnPhantom main.cu)
# 给 LearnPhantom 程序单独加入 PhantomFHE 头文件目录
target_include_directories(LearnPhantom PRIVATE
${PHANTOM_ROOT}/include
)
target_link_libraries(LearnPhantom PRIVATE Phantom)
最后的配置如下:

二、基于 PhantomFHE 的多项式运算实现
我们使用 PhantomFHE 库的 CKKS 方案,同态地加密计算下面这个方程:
( 1.23 × 4.56 ) 2 + ( 7.89 × 10.11 ) 2 + ( 12.13 × 14.15 ) 2 (1.23 \times 4.56)^2+(7.89 \times 10.11)^2+(12.13 \times 14.15)^2 (1.23×4.56)2+(7.89×10.11)2+(12.13×14.15)2
1. 导入依赖
在 main.cu 文件中导入必要的依赖库。这里需要注意的是,.cu 文件是 NVIDIA CUDA 程序的源代码文件,用于编写包含 CUDA 相关代码的程序,因此不要将其替换为普通的 .cpp 文件。
cpp
#include <iostream>
#include <random>
#include "phantom.h"
using namespace std;
using namespace phantom;
using namespace phantom::arith;
using namespace phantom::util;
2. 参数打印函数
为了打印一些参数信息,我复用了原代码 phantom-fhe 中的两个辅助函数。使用时,直接将这两个函数直接复制到 main 函数之前:
- 打印密文参数:
cpp
void print_parameters(const PhantomContext &context) {
auto &context_data = context.get_context_data(0);
string scheme_name;
switch (context_data.parms().scheme()) {
case phantom::scheme_type::bfv:
scheme_name = "BFV";
break;
case phantom::scheme_type::ckks:
scheme_name = "CKKS";
break;
case phantom::scheme_type::bgv:
scheme_name = "BGV";
break;
default:
throw invalid_argument("unsupported scheme");
}
cout << "/" << endl;
cout << "| Encryption parameters :" << endl;
cout << "| scheme: " << scheme_name << endl;
cout << "| poly_modulus_degree: " << context_data.parms().poly_modulus_degree() << endl;
cout << "| coeff_modulus size: ";
cout << context_data.total_coeff_modulus_bit_count() << " (";
auto parms = context_data.parms();
auto &coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
for (size_t i = 0; i < coeff_modulus_size - 1; i++) {
cout << coeff_modulus[i].bit_count() << " + ";
}
cout << coeff_modulus.back().bit_count();
cout << ") bits" << endl;
if (context_data.parms().scheme() == phantom::scheme_type::bfv) {
cout << "| plain_modulus: " << context_data.parms().plain_modulus().value() << endl;
}
cout << "\\" << endl;
}
- 打印明文向量:
cpp
template<typename T>
void print_vector(vector<T> vec, size_t print_size = 4, int prec = 3) {
ios old_fmt(nullptr);
old_fmt.copyfmt(cout);
size_t slot_count = vec.size();
cout << fixed << setprecision(prec);
cout << endl;
if (slot_count <= 2 * print_size) {
cout << " [";
for (size_t i = 0; i < slot_count; i++) {
cout << " " << vec[i] << ((i != slot_count - 1) ? "," : " ]\n");
}
}
else {
vec.resize(max(vec.size(), 2 * print_size));
cout << " [";
for (size_t i = 0; i < print_size; i++) {
cout << " " << vec[i] << ",";
}
if (vec.size() > 2 * print_size) {
cout << " ...,";
}
for (size_t i = slot_count - print_size; i < slot_count; i++) {
cout << " " << vec[i] << ((i != slot_count - 1) ? "," : " ]\n");
}
}
cout << endl;
cout.copyfmt(old_fmt);
}
3. 编写代码
- 参数设置
cpp
int alpha = 2;
size_t poly_modulus_degree = 1 << 15;
double scale = pow(2.0, 40);
vector<int> modulus_chain = {60, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 60, 60};
- 创建对象
cpp
EncryptionParameters parms(scheme_type::ckks); // 参数对象
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, modulus_chain));
parms.set_special_modulus_size(alpha); // 拿出 modulus_chain 中最后 alpha 个素数作为 key-switching 的模数,其余素数用于普通密文计算
PhantomContext context(parms); // 上下文对象
PhantomSecretKey secret_key(context); // 私钥
PhantomPublicKey public_key = secret_key.gen_publickey(context); // 公钥
PhantomRelinKey relin_key = secret_key.gen_relinkey(context); // 重线性化密钥
PhantomGaloisKey galois_key = secret_key.create_galois_keys(context); // 旋转密钥
PhantomCKKSEncoder encoder(context); // 编码器
size_t slot_count = encoder.slot_count();
cout << "密文参数: " << endl;
print_parameters(context);
- 设置明文
cpp
vector<double> input1(slot_count, 0.0);
input1[0] = 1.23;
input1[1] = 7.89;
input1[2] = 12.13;
vector<double> input2(slot_count, 0.0);
input2[0] = 4.56;
input2[1] = 10.11;
input2[2] = 14.15;
- 编码与加密
cpp
PhantomPlaintext plain_x1, plain_x2, plain_y; // 编码
encoder.encode(context, input1, scale, plain_x1);
encoder.encode(context, input2, scale, plain_x2);
PhantomCiphertext cipher_x1, cipher_x2; // 加密
public_key.encrypt_asymmetric(context, plain_x1, cipher_x1);
public_key.encrypt_asymmetric(context, plain_x2, cipher_x2);
- 同态计算:其实 PhantomFHE 中的函数与 SEAL 差不多,只不过 SEAL 中要用 evaluator 对象调用这些函数,PhantomFHE 中直接调用即可。
cpp
PhantomCiphertext mult = multiply(context, cipher_x1, cipher_x2); // 同态乘法
relinearize_inplace(context, mult, relin_key); // 重线性化
rescale_to_next_inplace(context, mult); // 重缩放
multiply_inplace(context, mult, mult);
relinearize_inplace(context, mult, relin_key);
rescale_to_next_inplace(context, mult);
PhantomCiphertext total = mult;
for (int i = 1; i < 3; ++i) {
PhantomCiphertext tmp = rotate_vector(context, mult, i, galois_key); // 旋转
add_inplace(context, total, tmp); // 相加
}
- 解密与解码:
cpp
cout << "同态计算输出: " << endl;
secret_key.decrypt(context, total, plain_y);
vector<double> result;
encoder.decode(context, plain_y, result);
print_vector(result, 3, 3);
最后,所有代码如下:
cpp
#include <iostream>
#include <random>
#include "phantom.h"
using namespace std;
using namespace phantom;
using namespace phantom::arith;
using namespace phantom::util;
void print_parameters(const PhantomContext &context) {
auto &context_data = context.get_context_data(0);
string scheme_name;
switch (context_data.parms().scheme()) {
case phantom::scheme_type::bfv:
scheme_name = "BFV";
break;
case phantom::scheme_type::ckks:
scheme_name = "CKKS";
break;
case phantom::scheme_type::bgv:
scheme_name = "BGV";
break;
default:
throw invalid_argument("unsupported scheme");
}
cout << "/" << endl;
cout << "| Encryption parameters :" << endl;
cout << "| scheme: " << scheme_name << endl;
cout << "| poly_modulus_degree: " << context_data.parms().poly_modulus_degree() << endl;
cout << "| coeff_modulus size: ";
cout << context_data.total_coeff_modulus_bit_count() << " (";
auto parms = context_data.parms();
auto &coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
for (size_t i = 0; i < coeff_modulus_size - 1; i++) {
cout << coeff_modulus[i].bit_count() << " + ";
}
cout << coeff_modulus.back().bit_count();
cout << ") bits" << endl;
if (context_data.parms().scheme() == phantom::scheme_type::bfv) {
cout << "| plain_modulus: " << context_data.parms().plain_modulus().value() << endl;
}
cout << "\\" << endl;
}
template<typename T>
void print_vector(vector<T> vec, size_t print_size = 4, int prec = 3) {
ios old_fmt(nullptr);
old_fmt.copyfmt(cout);
size_t slot_count = vec.size();
cout << fixed << setprecision(prec);
cout << endl;
if (slot_count <= 2 * print_size) {
cout << " [";
for (size_t i = 0; i < slot_count; i++) {
cout << " " << vec[i] << ((i != slot_count - 1) ? "," : " ]\n");
}
}
else {
vec.resize(max(vec.size(), 2 * print_size));
cout << " [";
for (size_t i = 0; i < print_size; i++) {
cout << " " << vec[i] << ",";
}
if (vec.size() > 2 * print_size) {
cout << " ...,";
}
for (size_t i = slot_count - print_size; i < slot_count; i++) {
cout << " " << vec[i] << ((i != slot_count - 1) ? "," : " ]\n");
}
}
cout << endl;
cout.copyfmt(old_fmt);
}
int main()
{
// 设置密文参数
int alpha = 2;
size_t poly_modulus_degree = 1 << 15;
double scale = pow(2.0, 40);
vector<int> modulus_chain = {60, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 60, 60};
// 创建对象
EncryptionParameters parms(scheme_type::ckks); // 参数对象
parms.set_poly_modulus_degree(poly_modulus_degree);
parms.set_coeff_modulus(CoeffModulus::Create(poly_modulus_degree, modulus_chain));
parms.set_special_modulus_size(alpha); // 拿出 modulus_chain 中最后 alpha 个素数作为 key-switching 的模数,其余素数用于普通密文计算
PhantomContext context(parms); // 上下文对象
PhantomSecretKey secret_key(context); // 私钥
PhantomPublicKey public_key = secret_key.gen_publickey(context); // 公钥
PhantomRelinKey relin_key = secret_key.gen_relinkey(context); // 重线性化密钥
PhantomGaloisKey galois_key = secret_key.create_galois_keys(context); // 旋转密钥
PhantomCKKSEncoder encoder(context); // 编码器
cout << "密文参数: " << endl;
print_parameters(context);
// 设置消息
size_t slot_count = encoder.slot_count();
// 同态计算
vector<double> input1(slot_count, 0.0);
input1[0] = 1.23;
input1[1] = 7.89;
input1[2] = 12.13;
vector<double> input2(slot_count, 0.0);
input2[0] = 4.56;
input2[1] = 10.11;
input2[2] = 14.15;
PhantomPlaintext plain_x1, plain_x2, plain_y;
encoder.encode(context, input1, scale, plain_x1);
encoder.encode(context, input2, scale, plain_x2);
PhantomCiphertext cipher_x1, cipher_x2;
public_key.encrypt_asymmetric(context, plain_x1, cipher_x1);
public_key.encrypt_asymmetric(context, plain_x2, cipher_x2);
PhantomCiphertext mult = multiply(context, cipher_x1, cipher_x2); // 同态乘法
relinearize_inplace(context, mult, relin_key); // 重线性化
rescale_to_next_inplace(context, mult); // 重缩放
multiply_inplace(context, mult, mult);
relinearize_inplace(context, mult, relin_key);
rescale_to_next_inplace(context, mult);
PhantomCiphertext total = mult;
for (int i = 1; i < 3; ++i) {
PhantomCiphertext tmp = rotate_vector(context, mult, i, galois_key); // 旋转
add_inplace(context, total, tmp); // 相加
}
cout << "同态计算输出: " << endl;
secret_key.decrypt(context, total, plain_y);
vector<double> result;
encoder.decode(context, plain_y, result);
print_vector(result, 3, 3);
return 0;
}
4. 代码执行
- 构建项目:在 WSL 终端中 cd 到项目文件夹,并执行以下命令
bash
cmake -S . -B build
cmake --build build -j
- 运行项目
bash
./build/LearnPhantom
- 执行结果:注意,我们在同态计算最后一步旋转得到的是一个向量,但只有第0位装载了正确结果。

Note:加密解密
注意,在我给的代码中,使用了非对称加密,但是 PhantomFHE 库还提供了对称加密的实现,二者的主要区别在于加解密所使用的密钥不同,下面举例说明:
- 设置消息:
cpp
size_t slot_count = encoder.slot_count();
vector<cuDoubleComplex> input(slot_count);
double rand_real,rand_imag;
for (size_t i = 0; i < slot_count; i++) {
rand_real = (double) rand() / RAND_MAX;
rand_imag = (double) rand() / RAND_MAX;
input[i] = make_cuDoubleComplex(rand_real, rand_imag);
}
cout << "输入消息: " << endl;
print_vector(input, 3, 3);
- 对称加密:用私钥加解密
cpp
PhantomPlaintext x_symmetric_plain;
encoder.encode(context, input, scale, x_symmetric_plain, 1); // 编码:上下文、要编码的消息、缩放因子、输出的明文、modulus chain level
PhantomCiphertext x_symmetric_cipher;
secret_key.encrypt_symmetric(context, x_symmetric_plain, x_symmetric_cipher); // 私钥加密
secret_key.decrypt(context, x_symmetric_cipher, x_symmetric_plain); // 私钥解密
vector<cuDoubleComplex> result_symmetric;
encoder.decode(context, x_symmetric_plain, result_symmetric); // 解码
- 非对称加密:公钥加密,私钥解密
cpp
PhantomPlaintext x_asymmetric_plain;
encoder.encode(context, input, scale, x_asymmetric_plain, 1); // 编码
PhantomCiphertext x_asymmetric_cipher;
public_key.encrypt_asymmetric(context, x_asymmetric_plain, x_asymmetric_cipher); // 公钥加密
secret_key.decrypt(context, x_asymmetric_cipher, x_asymmetric_plain); // 私钥解密
vector<cuDoubleComplex> result_asymmetric;
encoder.decode(context, x_asymmetric_plain, result_asymmetric); // 解码
不同加密方式下的结果输出一致:
