Miller-Rabin算法&乘法逆元

这周是另一个素性测试的算法,以及密码学中常用的求乘法逆元的算法、

素数的两个性质

  1. 任意素数n可表示为n = 2k+1,k>=0,q为奇数
    • 特殊情况:n = 2时, 2 = 20+1
  2. n是素数,a是小于n的正整数,则a2 mod n = 1 当且仅当a mod n = 1或 a mod n = n-1
    • 充分性:a2 mod n = (a mod n) * (a mod n) = 1
    • 必要性:a2 mod n =1 , 则a2 -1 mod n = 0;
    • 即(a+1)(a-1) mod n =0
    • ∵ n为素数,∴ (a+1) mod n =0 或 (a-1) mod n =0;
    • 即得出a mod n = 1或 a mod n = n-1

Miller-Rabin算法

n = 2k + 1 (k > 0 , q为奇数) 是大于2的素数,a是大于1且小于n-1的整数,则如下两个条件之一成立

  • aq mod n = 1
  • 存在j (j>=1 && j<=k),满足a2j-1q mod n = n-1

原理

费马小定理

an-1 mod n = a 2kq mod n = 1

序列:aq mod n,a2q mod n,a4q mod n,......,a 2k-1q mod n,a 2kq mod n

该序列有几个特点:

  • 后一项恰为前一项的平方
  • 最后一项为1

所以,可以得出Miller-Rabin算法的结论;

  • 该序列所有项均为1
    • 则aq mod n = 1
  • 序列存在一项为n-1,使之后所有项为1
    • 存在j (j>=1 && j<=k),满足a2j-1q mod n = n-1

Miller-Rabin算法实现

  • 确定整数k和q , 满足n = 2kq + 1
  • 随机选择整数a , 满足a>1且 a<n-1
  • 若aq mod n = 1,返回不确定
  • 若存在j (j>=1 && j<=k),满足a2j-1q mod n = n-1,返回不确定
  • 返回 "合数"

通过Miller-Rabin素性测试的数不一定是素数; 无法通过MillerRabin素性测试的数一定不是素数(必要条件)

代码实现

std::string miller_rabin_prime_test(unsigned int n, unsigned int a) {
	// TODO: 对n进行非确定性素性测试

	//先特判下
	if (a <= 1 || a >= n - 1)	return "error";
	if (n == 2)	return "uncertain";			//2返回uncertain,虽然我们知道2是素数
	if (n & 1 == 0)	return "not_prime";		//除2外的偶数,不是素数
	if (n == 1)	return "not_prime";			//1不是素数

	//将n分解为2^k * q + 1
	int k = 1;
	int q = 1;
	size_t t = n - 1;
	while (((t / 2) & 1) == 0)	//除2后仍为偶数
	{
		k++;
		t /= 2;
	}
	q = t;
	
	//判定1
	if (mod_exp(a, q, n) == 1) return "uncertain";

	//判定2
	size_t temp = mod_exp(a, q, n);
	for (int j = 1; j <= k; j++)
	{
		if (temp % n == n - 1)	return "uncertain";
		temp = (temp * temp) % n;
	}

	return "not_prime";
}

乘法逆元

存在条件:a和m的最大公约数为1(a与m互素),记为gcd(a,m) = 1

  • 若a与m互素,则存在整数k1,k2,满足k1*a + k2*m = 1
  • 两边同取mod m得:k1*a = 1 mod m
  • 即k1是a关于模m的乘法逆元

欧几里得算法

辗转相除,求最大公约数

比如gcd(a,b):若b!=0

则gcd(b,a%b)

欧几里得扩展算法

在欧几里得算法的基础上,计算辗转相除的系数

实现代码:

int ex_gcd(int a, int m, int &x, int &y) {
	if (m == 0) {
		x = 1; y = 0;
		return a; // 到达递归边界返回上一层
	}
	int r = ex_gcd(m, a % m, x, y);
	int t = x; x = y; y = t - a / m * y;
	return r; // 得到最大公约数
}

解释:

  • r = x*m + y*(a%m)
  • ∴ r = x*m + y*(a - a/m *m)
  • r = y*a + (x - a/m *y)*m
  • 所以需要有t......