2021SC@SDUSC
2021-10-31
第五周完成事项
工作内容
这周的主要工作是与李东晓弄清楚整体代码中的整数运算,我主要负责的是numth.h
部分
整数运算
通过阅读陈智罡教授写过的博客http://blog.sciencenet.cn/blog-411071-617182.html,我对于整数运算在全同态加密的作用有了一定的认识。在公钥和密钥的生成、明文加密以及密文解密过程中都需要整数的运算。举个陈智罡教授博客中的一个例子,假设已知明文为m,通过加密算法得到m+2r+pq,想要对其解密就需要先通过模p运算把pq消去,模2运算把2r消去,最后剩下明文m。
代码分析
这次主要分析两个功能,一个naf,全称是non-adjacent form,中文翻译是非相邻形式;另外则是获取两个数的最大公约数。
SEAL_NODISCARD inline std::vector<int> naf(int value)
{
std::vector<int> res;
// Record the sign of the original value and compute abs
bool sign = value < 0;
value = std::abs(value);
// Transform to non-adjacent form (NAF)
for (int i = 0; value; i++)
{
int zi = (value & int(0x1)) ? 2 - (value & int(0x3)) : 0;
value = (value - zi) >> 1;
if (zi)
{
res.push_back((sign ? -zi : zi) * (1 << i));
}
}
return res;
}
naf的作用是获取数字的非相邻形式,其中非零值不能相邻,举个例子,7的二进制表示可以有很多种,比如(0 1 1 1)= 4 + 2 + 1 = 7,(1 0 -1 1)= 8 - 2 + 1 = 7, (1 -1 1 1)= 8 - 4 + 2 + 1 = 7, (1 0 0 -1)= 8 - 1 = 7,但是只有最终表示(1 0 0 -1)是非相邻形式,非相邻形式也成为“规范有符号数字”表示。NAF的主要好处是该值的汉明权重将是最小的,可以加速乘法运算。
因为数的naf的结果是一段二进制串,所以使用向量进行存储。
bool sign = value < 0;
value = std::abs(value);
首先是判断输入数的正负性,符号不同最后会有相应的改变。如果是负数,会将结果二进制串的符号反转。
for (int i = 0; value; i++)
{
int zi = (value & int(0x1)) ? 2 - (value & int(0x3)) : 0;
value = (value - zi) >> 1;
if (zi)
{
res.push_back((sign ? -zi : zi) * (1 << i));
}
}
这个便是依次循环直至求出数的naf表示,sign ? -zi : zi
便是判断正负,改变整体二进制串的符号。
SEAL_NODISCARD inline std::uint64_t gcd(std::uint64_t x, std::uint64_t y)
{
#ifdef SEAL_DEBUG
if (x == 0)
{
throw std::invalid_argument("x cannot be zero");
}
if (y == 0)
{
throw std::invalid_argument("y cannot be zero");
}
#endif
if (x < y)
{
return gcd(y, x);
}
else if (y == 0)
{
return x;
}
else
{
std::uint64_t f = x % y;
if (f == 0)
{
return y;
}
else
{
return gcd(y, f);
}
}
}
SEAL_NODISCARD inline auto xgcd(std::uint64_t x, std::uint64_t y)
-> std::tuple<std::uint64_t, std::int64_t, std::int64_t>
{
#ifdef SEAL_DEBUG
if (x == 0)
{
throw std::invalid_argument("x cannot be zero");
}
if (y == 0)
{
throw std::invalid_argument("y cannot be zero");
}
#endif
std::int64_t prev_a = 1;
std::int64_t a = 0;
std::int64_t prev_b = 0;
std::int64_t b = 1;
while (y != 0)
{
std::int64_t q = util::safe_cast<std::int64_t>(x / y);
std::int64_t temp = util::safe_cast<std::int64_t>(x % y);
x = y;
y = util::safe_cast<std::uint64_t>(temp);
temp = a;
a = util::sub_safe(prev_a, util::mul_safe(q, a));
prev_a = temp;
temp = b;
b = util::sub_safe(prev_b, util::mul_safe(q, b));
prev_b = temp;
}
return std::make_tuple(x, prev_a, prev_b);
}
gcd是获取x与y的最大公约数。xgcd是相较于gcd的一个扩展,作用是返回(gcd,x,y),其中gcd是a和b的最大公约数。数字x,y使得gcd = ax + by
#ifdef SEAL_DEBUG
if (x == 0)
{
throw std::invalid_argument("x cannot be zero");
}
if (y == 0)
{
throw std::invalid_argument("y cannot be zero");
}
其中上述代码是gcd和xgcd里面通用的代码,主要内容是判断输入的x和y是否为0,x和y非零是通过数学求法——辗转相除法得知的,这个算法也是上述gcd算法的核心思想,这里我简单介绍一下辗转相除法。辗转相除法, 又名欧几里德算法,是求最大公约数的一种方法。它的具体做法是:用较小数除较大数,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数。
if (x < y)
{
return gcd(y, x);
}
else if (y == 0)
{
return x;
}
else
{
std::uint64_t f = x % y;
if (f == 0)
{
return y;
}
else
{
return gcd(y, f);
}
}
从gcd算法的核心内容可以得知,使用的是辗转相除法,如果x < y,则gcd(y ,x),因为算法要求x > y,如果y == 0,则算法递归结束返回x,剩下的情况便是x模y得余数,判断是结束递归还是继续递归。
总结
这周完成内容比较少,主要时间去写两篇3000字的认识实习报告了,下周会加快进度的。
最后,感谢孔老师的指导,感谢戴老师和其他审核老师的阅读!