2021SC@SDUSC
2021-10-24
第四周完成事项
工作分工
由于上周的代码难度比较大,所以这周的工作承接上周,不在布置新的工作。
代码分析
上周的重点主要是了解RLWE(带有错误的环运算)对于SEAL全同态加密算法的作用,对于代码的整体结构只是进行了初步分析,本周将详细的解读代码的细节内容。
首先便是对于生成多项式并保存在余数空间的代码的分析,这里举的是生成统一的三元多项式的例子。
void sample_poly_ternary(
shared_ptr<UniformRandomGenerator> prng, const EncryptionParameters &parms, uint64_t *destination)
{
auto coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
RandomToStandardAdapter engine(prng);
uniform_int_distribution<uint64_t> dist(0, 2);
SEAL_ITERATE(iter(destination), coeff_count, [&](auto &I) {
uint64_t rand = dist(engine);
uint64_t flag = static_cast<uint64_t>(-static_cast<int64_t>(rand == 0));
SEAL_ITERATE(
iter(StrideIter<uint64_t *>(&I, coeff_count), coeff_modulus), coeff_modulus_size,
[&](auto J) { *get<0>(J) = rand + (flag & get<1>(J).value()) - 1; });
});
}
之前的代码分析仅仅是对每个过程都干了什么进行了基础介绍,这里将具体展开每个部分的作用。parms.coeff_modulus()
获取的coeff_modulus为当前设置的系数模数参量;parms.poly_modulus_degree()
获取的是多项式模参数的次数。这些参数的作用都是为了多项式的产生。
RandomToStandardAdapter engine(prng);
目的是获取C++当中随机数生成的随机源,为了后续遍历的时候生成有效随机数uint64_t rand = dist(engine);
uniform_int_distribution<uint64_t> dist(0, 2);
生成指定范围随机数的变量。
SEAL_ITERATE(iter(destination), coeff_count, [&](auto &I)
遍历器,根据目标多项式的大小依次进行遍历。
然后是对公钥生成的代码详细分析
void encrypt_zero_asymmetric(
const PublicKey &public_key, const SEALContext &context, parms_id_type parms_id, bool is_ntt_form,Ciphertext &destination)
{
#ifdef SEAL_DEBUG
if (!is_valid_for(public_key, context))
{
throw invalid_argument("public key is not valid for the encryption parameters");
}
#endif
// We use a fresh memory pool with `clear_on_destruction' enabled
MemoryPoolHandle pool = MemoryManager::GetPool(mm_prof_opt::mm_force_new, true);
auto &context_data = *context.get_context_data(parms_id);
auto &parms = context_data.parms();
auto &coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
auto ntt_tables = context_data.small_ntt_tables();
size_t encrypted_size = public_key.data().size();
// Make destination have right size and parms_id
// Ciphertext (c_0,c_1, ...)
destination.resize(context, parms_id, encrypted_size);
destination.is_ntt_form() = is_ntt_form;
destination.scale() = 1.0;
// c[j] = public_key[j] * u + e[j] where e[j] <-- chi, u <-- R_3
// Create a PRNG; u and the noise/error share the same PRNG
auto prng = parms.random_generator()->create();
// Generate u <-- R_3
auto u(allocate_poly(coeff_count, coeff_modulus_size, pool));
sample_poly_ternary(prng, parms, u.get());
// c[j] = u * public_key[j]
for (size_t i = 0; i < coeff_modulus_size; i++)
{
ntt_negacyclic_harvey(u.get() + i * coeff_count, ntt_tables[i]);
for (size_t j = 0; j < encrypted_size; j++)
{
dyadic_product_coeffmod(
u.get() + i * coeff_count, public_key.data().data(j) + i * coeff_count, coeff_count,coeff_modulus[i], destination.data(j) + i * coeff_count);
// Addition with e_0, e_1 is in non-NTT form
if (!is_ntt_form)
{
inverse_ntt_negacyclic_harvey(destination.data(j) + i * coeff_count, ntt_tables[i]);
}
}
}
// Generate e_j <-- chi
// c[j] = public_key[j] * u + e[j]
for (size_t j = 0; j < encrypted_size; j++)
{
SEAL_NOISE_SAMPLER(prng, parms, u.get());
for (size_t i = 0; i < coeff_modulus_size; i++)
{
// Addition with e_0, e_1 is in NTT form
if (is_ntt_form)
{
ntt_negacyclic_harvey(u.get() + i * coeff_count, ntt_tables[i]);
}
add_poly_coeffmod(
u.get() + i * coeff_count, destination.data(j) + i * coeff_count, coeff_count, coeff_modulus[i],destination.data(j) + i * coeff_count);
}
}
}
首先是判断是否bug就不再过多讲解了。
第一步肯定是在内存池开辟一片空间,用于结果的存储,因为在内存的运算速度会更快。
// We use a fresh memory pool with `clear_on_destruction' enabled
MemoryPoolHandle pool = MemoryManager::GetPool(mm_prof_opt::mm_force_new, true);
auto &context_data = *context.get_context_data(parms_id);
auto &parms = context_data.parms();
auto &coeff_modulus = parms.coeff_modulus();
size_t coeff_modulus_size = coeff_modulus.size();
size_t coeff_count = parms.poly_modulus_degree();
auto ntt_tables = context_data.small_ntt_tables();
size_t encrypted_size = public_key.data().size();
第二步根据传进来的参数更新一下目标密文的大小与编号。
// Make destination have right size and parms_id
// Ciphertext (c_0,c_1, ...)
destination.resize(context, parms_id, encrypted_size);
destination.is_ntt_form() = is_ntt_form;
destination.scale() = 1.0;
第三步生成一个prng,这时候我们可以回去看生成统一三元多项式的代码,里面也有shared_ptr<UniformRandomGenerator> prng
以及RandomToStandardAdapter engine(prng);
,便可以明白prng的作用,在理想状态下是不存在噪声与错误的,但是现实生活中常常会有各种各样的问题影响我们实验的结果,所以这里生成一个随机数的随机源,后面用来生成噪声,表示我们日常生活中的一般状况。
// c[j] = public_key[j] * u + e[j] where e[j] <-- chi, u <-- R_3
// Create a PRNG; u and the noise/error share the same PRNG
auto prng = parms.random_generator()->create();
第四步是调用之前定义的生成统一的三元多项式的函数生成三元多项式,这里同时说明了多项式和噪声拥有统一的随机源,保证了结果的一致性。
// Generate u <-- R_3
auto u(allocate_poly(coeff_count, coeff_modulus_size, pool));
sample_poly_ternary(prng, parms, u.get());
第五步便是根据系数模数的大小以及加密的大小进行双重循环生成目标密文。
// c[j] = u * public_key[j]
for (size_t i = 0; i < coeff_modulus_size; i++)
{
ntt_negacyclic_harvey(u.get() + i * coeff_count, ntt_tables[i]);
for (size_t j = 0; j < encrypted_size; j++)
{
dyadic_product_coeffmod(
u.get() + i * coeff_count, public_key.data().data(j) + i * coeff_count, coeff_count,coeff_modulus[i], destination.data(j) + i * coeff_count);
// Addition with e_0, e_1 is in non-NTT form
if (!is_ntt_form)
{
inverse_ntt_negacyclic_harvey(destination.data(j) + i * coeff_count, ntt_tables[i]);
}
}
}
// Generate e_j <-- chi
// c[j] = public_key[j] * u + e[j]
for (size_t j = 0; j < encrypted_size; j++)
{
SEAL_NOISE_SAMPLER(prng, parms, u.get());
for (size_t i = 0; i < coeff_modulus_size; i++)
{
// Addition with e_0, e_1 is in NTT form
if (is_ntt_form)
{
ntt_negacyclic_harvey(u.get() + i * coeff_count, ntt_tables[i]);
}
add_poly_coeffmod(
u.get() + i * coeff_count, destination.data(j) + i * coeff_count, coeff_count, coeff_modulus[i],destination.data(j) + i * coeff_count);
}
}
总结
第四周由于机器学习实验导致事件比较匆忙,所以没有来的及布置新的内容。正好上次的代码没有进行深度分析,可以将功补过了。
下次一定会提前做好工作分工,做好组长的职责。
最后,感谢孔老师的指导,感谢戴老师和其他审核老师的阅读!