SEAL全同态加密开源库(六) 整数运算numth源码解析

SEAL全同态加密开源库(六) 整数运算numth源码解析

2021SC@SDUSC

2021-11-07

前言

这是SEAL开源库代码分析报告第五篇,本篇将首先照例引入一些理论知识,然后本篇将开始分析整数运算部分,numth.cpp的源代码。关于其重要性,在公钥和密钥的生成、明文加密以及密文解密过程中,都需要整数的运算。

举个陈智罡教授博客中的例子,假设已知明文为m,通过加密算法得到m+2r+pq,想要对其解密就需要先通过模p运算把pq消去,模2运算把2r消去,最后剩下明文m。因此整数运算这一部分,有仔细分析的必要。

理论知识补充

推荐一篇很好的初学者入门博客,陈志罡教授写的:科学网—整数上全同态加密方案分析(1)–献给全同态加密的初学者 - 陈智罡的博文 (sciencenet.cn)

本期的理论知识补充,我们介绍整数上的全同态加密。

Enc(m): m+2r+pq

Dec©: (c mod p) mod 2=(c – p*「c/p」)mod 2 = Lsb© XOR Lsb(「c/p」)

上面这个加密方案显然是正确的,模p运算把pq消去,模2运算把2r消去,最后剩下明文m 。这个公式看上去很简单,但是却很耐看,需要多看看。

公式中的p是一个正的奇数,q是一个大的正整数(没有要求是奇数,它比p要大的多),p 和q在密钥生成阶段确定,p看成是密钥。而r是加密时随机选择的一个小的整数(可以为负数)。明文m∈ {0,1},是对“位”进行加密的,所得密文是整数。

上面方案的明文空间是{0,1},密文空间是整数集。

全同态加密方案中除了加密、解密还有一个非常重要的算法就是:Evaluate,它的作用就是对于给定的功能函数f以及密文c1,c2,…,ct等做运算f (c1,c2,…,ct)。在这里就是对密文做相应的整数加、减、乘运算。

以上方案可以看成是对称加密方案。下面来考虑公钥加密方案。其实把pq看成公钥就OK。由于q是公开的,所以如果把pq看成公钥,私钥p立刻就被知道了(p=pq/q)。怎么办呢?看上面加密算法中,当对明文0进行加密时,密文为2r+pq, 所以我们可以做一个集合{xi;xi=2ri+pqi},公钥pk就是这个集合{xi},加密时随机的从{xi}中选取一个子集S,按如下公式进行加密:

Enc(m): m+2r+sum(S); 其中sum(S)表示对S中的xi进行求和。

由于sum(S)是一些0的加密密文之和,所以对解密并不影响,整个解密过程不变。

这个方案是安全的,就是我们所说的DGHV方案。

numth.cpp源码分析

numth.h文件的解析,详见刘云聪同学的博客。按照分工,我主要负责分析numth.cpp文件。

先是函数conjugate_classes,是通过模数和子群生成器得到群的共轭类。

头文件声明如下:

SEAL_NODISCARD std::vector<std::uint64_t> conjugate_classes(
            std::uint64_t modulus, std::uint64_t subgroup_generator);

先介绍一下如何通过模数和子群生成器得到共轭类:

首先对于共轭类进行了初始化,将与模数互为质数的值放入共轭类:

vector<uint64_t> classes{};
for (uint64_t i = 0; i < modulus; i++)
{
	if (gcd(i, modulus) > 1)
    {
		classes.push_back(0);
    }
    else
    {
        classes.push_back(i);
    }
}

然后开始迭代更新共轭类中的数值。先判断该位置是否为零,如果为零,说明该位置和模数不互为质数,直接continue。然后判断共轭类第i个位置的值是否为i,如果不是,说明i不是一个支点。剩下的可能性,就是i是一个支点,然后通过子群生成器和模数构建一个j,以i为支点迭代j对应位置的值。最后返回共轭类。

for (uint64_t i = 0; i < modulus; i++)
            {
                if (classes[static_cast<size_t>(i)] == 0)
                {
                    continue;
                }
                if (classes[static_cast<size_t>(i)] < i)
                {
                    // 如果i不是一个支点,更新它的支点
                    classes[static_cast<size_t>(i)] = classes[static_cast<size_t>(classes[static_cast<size_t>(i)])];
                    continue;
                }
                // 如果 i 是一个支点,更新其他支点以指向它
                uint64_t j = (i * subgroup_generator) % modulus;
                while (classes[static_cast<size_t>(j)] != i)
                {
                    // Merge the equivalence classes of j and i
                    // Note: if classes[j] != j then classes[j] will be updated later,
                    // when we get to i = j and use the code for "i not pivot".
                    classes[static_cast<size_t>(classes[static_cast<size_t>(j)])] = i;
                    j = (j * subgroup_generator) % modulus;
                }
            }

函数is_prime就是判断是否为素数:

首先检查最简单的情况:
代码来看就是一堆0-13的素数判定。

然后,作所谓的米勒-拉宾试验。

米勒-拉宾素性检验是一种素数判定法则,利用[随机化算法]判断一个数是[合数]还是可能是素数。

下面这个截图比较能说明其原理:
在这里插入图片描述

体现在代码上,就是如下的代码:

uint64_t d = value - 1;
            uint64_t r = 0;
            while (0 == (d & 0x1))
            {
                d >>= 1;
                r++;
            }
            if (r == 0)
            {
                return false;
            }

            // 1) Pick a = 2, check a^(value - 1).
            // 2) Pick a randomly from [3, value - 1], check a^(value - 1).
            // 3) Repeat 2) for another num_rounds - 2 times.
            random_device rand;
            uniform_int_distribution<unsigned long long> dist(3, value - 1);
            for (size_t i = 0; i < num_rounds; i++)
            {
                uint64_t a = i ? dist(rand) : 2;
                uint64_t x = exponentiate_uint_mod(a, d, modulus);
                if (x == 1 || x == value - 1)
                {
                    continue;
                }
                uint64_t count = 0;
                do
                {
                    x = multiply_uint_mod(x, x, modulus);
                    count++;
                } while (x != value - 1 && count < r - 1);
                if (x != value - 1)
                {
                    return false;
                }
            }

后记

还剩一部分源码没有分析完毕,会在下一篇博客解决这一部分的战斗。目前的进度有些怠慢,继续努力!

猜你喜欢

转载自blog.csdn.net/ldxcsdn/article/details/121199799