跳转至

离散对数

定义

前置知识:阶与原根

离散对数的定义方式和对数类似。取有原根的正整数模数 m,设其一个原根为 g. 对满足 (a,m)=1 的整数 a,我们知道必存在唯一的整数 0k<φ(m) 使得

gka(modm)

我们称这个 k 为以 g 为底,模 m 的离散对数,记作 k=indga,在不引起混淆的情况下可记作 inda.

显然 indg1=0indgg=1.

性质

离散对数的性质也和对数有诸多类似之处。

性质

g 是模 m 的原根,(a,m)=(b,m)=1,则:

  1. indg(ab)indga+indgb(modφ(m))

    进而 (nN),  indgannindga(modφ(m))

  2. g1 也是模 m 的原根,则 indgaindg1aindgg1(modφ(m))

  3. ab(modm)indga=indgb
证明
  1. gindg(ab)abgindgagindgbgindga+indgb(modm)
  2. x=indg1a,则 ag1x(modm). 又令 y=indgg1,则 g1gy(modm).

    agxy(modm),即 indgaxyindg1aindgg1(modφ(m))

  3. 注意到

    indga=indgbindgaindgb(modφ(m))gindgagindgb(modm)ab(modm)

大步小步算法

目前离散对数问题仍不存在多项式时间经典算法(离散对数问题的输入规模是输入数据的位数)。在密码学中,基于这一点人们设计了许多非对称加密算法,如 Ed25519

在算法竞赛中,BSGS(baby-step giant-step,大步小步算法)常用于求解离散对数问题。形式化地说,对 a,b,mZ+,该算法可以在 O(m) 的时间内求解

axb(modm)

其中 am。方程的解 x 满足 0x<m.(注意 m 不一定是素数)

算法描述

x=AmB,其中 0A,Bm,则有 aAmBb(modm),稍加变换,则有 aAmbaB(modm).

我们已知的是 a,b,所以我们可以先算出等式右边的 baB 的所有取值,枚举 B,用 hash/map 存下来,然后逐一计算 aAm,枚举 A,寻找是否有与之相等的 baB,从而我们可以得到所有的 xx=AmB.

注意到 A,B 均小于 m,所以时间复杂度为 Θ(m),用 map 则多一个 log.

为什么要求 am 互质

注意到我们求出的是 A,B,我们需要保证从 aAmbaB(modm) 可以推回 aAmBb(modm),后式是前式左右两边除以 aB 得到,所以必须有 aBmam.

进阶篇

a,bZ+pP,求解

xab(modp)

该问题可以转化为 BSGS 求解的问题。

由于式子中的模数 p 是一个质数,那么 p 一定存在一个原根 g. 因此对于模 p 意义下的任意的数 x (1x<p) 有且仅有一个数 i (0i<p1) 满足 x=gi.

方法一

我们令 x=gcgp 的原根(我们一定可以找到这个 gc),问题转化为求解 (gc)ab(modp). 稍加变换,得到

(ga)cb(modp)

于是就转换成了 BSGS 的基本模型了,可以在 O(p) 解出 c,这样可以得到原方程的一个特解 x0gc(modp).

方法二

我们仍令 x=gc,并且设 b=gt,于是我们得到

gacgt(modp)

方程两边同时取离散对数得到

act(modφ(p))

我们可以通过 BSGS 求解 gtb(modp) 得到 t,于是这就转化成了一个线性同余方程的问题。这样也可以解出 c,求出 x 的一个特解 x0gc(modp).

找到所有解

在知道 x0gc(modp) 的情况下,我们想得到原问题的所有解。首先我们知道 gφ(p)1(modp),于是可以得到

 tZ, xagca+tφ(p)b(modp)

于是得到所有解为

 tZ,atφ(p), xgc+tφ(p)a(modp)

对于上面这个式子,显然有 a(a,φ(p))t. 因此我们设 t=a(a,φ(p))i,得到

 iZ,xgc+φ(p)(a,φ(p))i(modp)

这就是原问题的所有解。

实现

下面的代码实现的找原根、离散对数解和原问题所有解的过程。

参考代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
int gcd(int a, int b) { return a ? gcd(b % a, a) : b; }

int powmod(int a, int b, int p) {
  int res = 1;
  while (b > 0) {
    if (b & 1) res = res * a % p;
    a = a * a % p, b >>= 1;
  }
  return res;
}

// Finds the primitive root modulo p
int generator(int p) {
  vector<int> fact;
  int phi = p - 1, n = phi;
  for (int i = 2; i * i <= n; ++i) {
    if (n % i == 0) {
      fact.push_back(i);
      while (n % i == 0) n /= i;
    }
  }
  if (n > 1) fact.push_back(n);
  for (int res = 2; res <= p; ++res) {
    bool ok = true;
    for (int factor : fact) {
      if (powmod(res, phi / factor, p) == 1) {
        ok = false;
        break;
      }
    }
    if (ok) return res;
  }
  return -1;
}

// This program finds all numbers x such that x^k=a (mod n)
int main() {
  int n, k, a;
  scanf("%d %d %d", &n, &k, &a);
  if (a == 0) return puts("1\n0"), 0;
  int g = generator(n);
  // Baby-step giant-step discrete logarithm algorithm
  int sq = (int)sqrt(n + .0) + 1;
  vector<pair<int, int>> dec(sq);
  for (int i = 1; i <= sq; ++i)
    dec[i - 1] = {powmod(g, i * sq * k % (n - 1), n), i};
  sort(dec.begin(), dec.end());
  int any_ans = -1;
  for (int i = 0; i < sq; ++i) {
    int my = powmod(g, i * k % (n - 1), n) * a % n;
    auto it = lower_bound(dec.begin(), dec.end(), make_pair(my, 0));
    if (it != dec.end() && it->first == my) {
      any_ans = it->second * sq - i;
      break;
    }
  }
  if (any_ans == -1) return puts("0"), 0;
  // Print all possible answers
  int delta = (n - 1) / gcd(k, n - 1);
  vector<int> ans;
  for (int cur = any_ans % delta; cur < n - 1; cur += delta)
    ans.push_back(powmod(g, cur, n));
  sort(ans.begin(), ans.end());
  printf("%zu\n", ans.size());
  for (int answer : ans) printf("%d ", answer);
}

扩展篇(扩展 BSGS)

a,b,mZ+,求解

axb(modm)

其中 a,m 不一定互质。

(a,m)=1 时,在模 m 意义下 a 存在逆元,因此可以使用 BSGS 算法求解。于是我们想办法让他们变得互质。

具体地,设 d1=(a,m). 如果 d1b,则原方程无解。否则我们把方程同时除以 d1,得到

ad1ax1bd1(modmd1)

如果 amd1 仍不互质就再除,设 d2=(a,md1). 如果 d2bd1,则方程无解;否则同时除以 d2 得到

a2d1d2ax2bd1d2(modmd1d2)

同理,这样不停的判断下去,直到 amd1d2dk.

D=i=1kdi,于是方程就变成了这样:

akDaxkbD(modmD)

由于 amD,于是推出 akDmD. 这样 akD 就有逆元了,于是把它丢到方程右边,这就是一个普通的 BSGS 问题了,于是求解 xk 后再加上 k 就是原方程的解啦。

注意,不排除解小于等于 k 的情况,所以在消因子之前做一下 Θ(k) 枚举,直接验证 aib(modm),这样就能避免这种情况。

习题

本页面部分内容以及代码译自博文 Дискретное извлечение корня 与其英文翻译版 Discrete Root。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。

参考资料

  1. Discrete logarithm - Wikipedia
  2. 潘承洞,潘承彪。初等数论。
  3. 冯克勤。初等数论及其应用。