2021SC@SDUSC
这次对格密码层进行一个分析
首先可以看到格密码层也是分了相当多的文件
从名字上直观认识,大多都是数学后端上功能以及一些辅助的运算模块
先来看看lattice中的poly模块
30 - 78
#include <cmath>
#include <fstream>
#include "lattice/backend.h"
#define DEMANGLER // used for the demangling type namefunction.
#ifdef DEMANGLER
#include <cxxabi.h>
#include <cstdlib>
#include <string>
template <typename T>
std::string type_name() {
int status;
std::string tname = typeid(T).name();
char *demangled_name =
abi::__cxa_demangle(tname.c_str(), nullptr, nullptr, &status);
if (status == 0) {
tname = demangled_name;
std::free(demangled_name);
}
return tname;
}
#endif
namespace lbcrypto {
template <typename VecType>
PolyImpl<VecType>::PolyImpl()
: m_values(nullptr), m_format(Format::EVALUATION) {}
template <typename VecType>
PolyImpl<VecType>::PolyImpl(const shared_ptr<PolyImpl::Params> params,
Format format, bool initializeElementToZero)
: m_values(nullptr), m_format(format) {
m_params = params;
if (initializeElementToZero) {
this->SetValuesToZero();
}
}
template <typename VecType>
PolyImpl<VecType>::PolyImpl(bool initializeElementToMax,
const shared_ptr<PolyImpl::Params> params,
Format format)
: m_values(nullptr), m_format(format) {
m_params = params;
if (initializeElementToMax) {
this->SetValuesToMax();
}
}
可以发先 精密的数学后端应该就是声明在 "lattice/backend.h"中的了 这部分的外观用途表示它是在声明大量的有关多项式的基本定义,以及对于不同vector的通用封装策略 用泛型来实现
其实说到这样的大型工程项目 不过是企业级的web框架还是说一些支撑着大型应用的后端 总是能发现很多的,很多的甚至是看起来没什么意义的封装。这些东西不是小型应用中经常出现的现象。但是其实这样的大型工程中做到不仅是对复杂功能上尽量的向用户友好(所以就有了这么多的重载,就有了不同的用户接口和简化的配置工作),也是为了作出一种开发者友好的环境。
在palisade的文档(wiki)中,描述的是此项目采用分层次的软件工程方法构建
那么格密码层以及精密的数学后端其实相当于不同层级上为开发者提供调用的"基础设施"了 这种底层的架构其实可能将比上层的东西更加难以把控具体需求 因为需要结合本身的工程目标和开发者的调用方便之利为一体的
这个时候我就发现 在这个格密码库的基础设施实现上 不仅是按照功能内聚 也是按照 "基础操作的重载集合" 来划分的
格密码加密的一个所基于的基本问题就是LWE
那么由这个问题衍生出的基本加密包来一窥其实现的思路
// classical LWE encryption
// a is a randomly uniform vector of dimension n; with integers mod q
// b = a*s + e + m floor(q/4) is an integer mod q
std::shared_ptr<LWECiphertextImpl> LWEEncryptionScheme::Encrypt(
const std::shared_ptr<LWECryptoParams> params,
const std::shared_ptr<const LWEPrivateKeyImpl> sk,
const LWEPlaintext &m) const {
NativeInteger q = sk->GetElement().GetModulus();
uint32_t n = sk->GetElement().GetLength();
NativeInteger b = (m % 4) * (q >> 2) + params->GetDgg().GenerateInteger(q);
DiscreteUniformGeneratorImpl<NativeVector> dug;
dug.SetModulus(q);
NativeVector a = dug.GenerateVector(n);
NativeInteger mu = q.ComputeMu();
const NativeVector &s = sk->GetElement();
for (uint32_t i = 0; i < n; ++i) {
b += a[i].ModMulFast(s[i], q, mu);
}
b.ModEq(q);
return std::make_shared<LWECiphertextImpl>(LWECiphertextImpl(a, b));
}
先来看看比较核心的加密部分
comment描述基本式为 b = a*s + e + m [q/4] 其中a是随机生成的混入部分
这里的实现便是遵照以上的式子进行构造的
// classical LWE decryption
// m_result = Round(4/q * (b - a*s))
void LWEEncryptionScheme::Decrypt(
const std::shared_ptr<LWECryptoParams> params,
const std::shared_ptr<const LWEPrivateKeyImpl> sk,
const std::shared_ptr<const LWECiphertextImpl> ct,
LWEPlaintext *result) const {
// TODO in the future we should add a check to make sure sk parameters match
// the ct parameters
// Create local variables to speed up the computations
NativeVector a = ct->GetA();
uint32_t n = sk->GetElement().GetLength();
NativeVector s = sk->GetElement();
NativeInteger q = sk->GetElement().GetModulus();
NativeInteger mu = q.ComputeMu();
NativeInteger inner(0);
for (uint32_t i = 0; i < n; ++i) {
inner += a[i].ModMulFast(s[i], q, mu);
}
inner.ModEq(q);
NativeInteger r = ct->GetB();
r.ModSubFastEq(inner, q);
// Alternatively, rounding can be done as
// *result = (r.MultiplyAndRound(NativeInteger(4),q)).ConvertToInt();
// But the method below is a more efficient way of doing the rounding
// the idea is that Round(4/q x) = q/8 + Floor(4/q x)
r.ModAddFastEq((q >> 3), q);
*result = ((NativeInteger(4) * r) / q).ConvertToInt();
#if defined(BINFHE_DEBUG)
double error = (4.0 * (r.ConvertToDouble() - q.ConvertToInt() / 8)) /
q.ConvertToDouble() -
static_cast<double>(*result);
std::cerr << "error:\t" << error << std::endl;
#endif
return;
}
解密基于m_result = Round(4/q * (b - a*s))
对于LWE问题能够作出正确的理解是理解上述代码的关键
那么这里对LWE问题进行一个回顾
其中错误可以叫作 [ 噪声 ]
这个噪声是构成复杂度的关键所在
我们再看看刚才用来加密的那个式子
b = a*s + e + m [q/4]
这个e便是混入的噪声
有了噪声e 那么就无法推出向量中的未知部分 那么就只能使用暴力破解进行解密(在没有其它信息的情况下) 去枚举所有可能的情况去代入式子 然后使其值去逼近b
我们可以发现 这样做的复杂度非常之高 所以这也是在新的计算环境下仍然可以奏效的加密举措
而在加密过程中 用到的是决策版本的LWE (又称DLWE)
这段话基本就是以上实现方法中所基于的理论体系
称之为Regev加密(因为是由Regev发明的)
正确性的关键乃是使x^在容忍范围内也就是使得噪音的影响不超过可被还原的返回
它的正确性q/4 > m * B这个强定义密不可分
那么从这里可以发现LWE系的问题衍生出的加密算法的关键在于能够正确的处理噪音
这就和我们前面说的BFV加密方式的可行性相关了起来 保持EVAL运算之后仍然能够让最后
还原出的密文是"可解析的",这里的关键可能仍然在于关键e的分布选取
那么可以基于这样的认知再重新回顾bfv,bgv,cck等 同态加密方法 是如何去处理e及还原它的
参照
LWE问题及其公钥密码方案 - 知乎 (zhihu.com)