Discrete Logarithm

Definition

Prerequisite knowledge: order and primitive roots.

Discrete logarithms are defined in a similar way to logarithms. Take the positive integer modulus mm with primitive roots, and let one of its primitive roots be gg. For the integer aa that satisfies (a,m)=1(a,m)=1, we know that there must be a unique integer 0≤k<φ(m)0\leq k<\varphi(m) such that

gk≡a(modm)g^k\equiv a\pmod m

We call this kk the discrete logarithm of gg modulo mm, denoted as k=ind⁡gak=\operatorname{ind}_g a, or ind⁡a\operatorname{ind} a without causing confusion.

Apparently ind⁡g1=0\operatorname{ind}_g 1=0, ind⁡gg=1\operatorname{ind}_g g=1.

Properties

The properties of discrete logarithms also have many similarities with logarithms.

Properties

Suppose gg is the primitive root modulo mm, and (a,m)=(b,m)=1(a,m)=(b,m)=1, then:

  1. ind⁡g(ab)≡ind⁡ga+ind⁡gb(modφ(m))\operatorname{ind}_g(ab)\equiv\operatorname{ind}_g a+\operatorname{ind}_g b\pmod{\varphi(m)}

    then (∀n∈N),  ind⁡gan≡nind⁡ga(modφ(m))(\forall n\in\mathbf{N}),~~\operatorname{ind}_g a^n\equiv n\operatorname{ind}_g a\pmod{\varphi(m)}

  2. If g1g_1 is also a primitive root modulo mm, then ind⁡ga≡ind⁡g1a⋅ind⁡gg1(modφ(m))\operatorname{ind}_g a\equiv\operatorname{ind}_{g_1}a \cdot \operatorname{ind}_g g_1\pmod{\varphi(m)}

  3. a≡b(modm)  ⟺  ind⁡ga=ind⁡gba\equiv b\pmod m\iff \operatorname{ind}_g a=\operatorname{ind}_g b

Proof
  1. gind⁡g(ab)≡ab≡gind⁡gagind⁡gb≡gind⁡ga+ind⁡gb(modm)g^{\operatorname{ind}_g(ab)}\equiv ab\equiv g^{\operatorname{ind}_g a}g^{\operatorname{ind}_g b}\equiv g^{\operatorname{ind}_g a+\operatorname{ind}_g b}\pmod m

  2. Let x=ind⁡g1ax=\operatorname{ind}_{g_1}a, then a≡g1x(modm)a\equiv g_1^x\pmod m. Let y=ind⁡gg1y=\operatorname{ind}_g g_1, then g1≡gy(modm)g_1\equiv g^y\pmod m.

    , so a≡gxy(modm)a\equiv g^{xy}\pmod m, that is, ind⁡ga≡xy≡ind⁡g1a⋅ind⁡gg1(modφ(m))\operatorname{ind}_g a\equiv xy\equiv\operatorname{ind}_{g_1}a \cdot \operatorname{ind}_g g_1\pmod{\varphi(m)}

  3. noted

    ind⁡ga=ind⁡gb  ⟺  ind⁡ga≡ind⁡gb(modφ(m))  ⟺  gind⁡ga≡gind⁡gb(modm)  ⟺  a≡b(modm)\begin{aligned} \operatorname{ind}_g a=\operatorname{ind}_g b&\iff \operatorname{ind}_g a\equiv\operatorname{ind}_g b\pmod{\varphi(m)}\\ &\iff g^{\operatorname{ind}_g a}\equiv g^{\operatorname{ind}_g b}\pmod m\\ &\iff a\equiv b\pmod m \end{aligned}

Baby-Step Giant-Step Algorithm

At present, there is still no polynomial time classical algorithm for the discrete logarithm problem (the input size of the discrete logarithm problem is the number of digits of the input data). In cryptography, based on this, people have designed many asymmetric encryption algorithms, such as Ed25519.

In algorithm competitions, BSGS (baby-step giant-step algorithm) is often used to solve discrete logarithm problems. Formally speaking, for a,b,m∈Z+a,b,m\in\mathbf{Z}^+, the algorithm can be solved in O(m)O(\sqrt{m}) time

ax≡b(modm)a^x \equiv b \pmod m

Here a⊥ma\perp m. The solution xx satisfies 0≤x<m0 \le x < m. (Note that mm is not necessarily a prime number)

Algorithm Description

Let x=A⌈m⌉−Bx = A \left \lceil \sqrt m \right \rceil - B, among them 0≤A,B≤⌈m⌉0\le A,B \le \left \lceil \sqrt m \right \rceil, then we have aA⌈m⌉−B≡b(modm)a^{A\left \lceil \sqrt m \right \rceil -B} \equiv b \pmod m, and with a slight transformation, we have aA⌈m⌉≡baB(modm)a^{A\left \lceil \sqrt m \right \rceil} \equiv ba^B \pmod m.

What we know is a,ba,b, so we can first calculate all the values ​​of baBba^B on the right side of the equation, enumerate BB, save it with hash/map, then calculate aA⌈m⌉a^{A\left \lceil \sqrt m \right \rceil} one by one, enumerate AA, and find whether there is an equal value baBba^B, so we can get all xx, x=A⌈m⌉−Bx=A \left \lceil \sqrt m \right \rceil - B.

Note that A,BA,B is smaller than ⌈m⌉\left \lceil \sqrt m \right \rceil, so the time complexity is Θ(m)\Theta\left (\sqrt m\right ), and using map will result in one more log⁡\log.

Why does > [!note]- require that aa and mm are relatively prime?

Note that what we find is A,BA,B. We need to ensure that aA⌈m⌉≡baB(modm)a^{A\left \lceil \sqrt m \right \rceil} \equiv ba^B \pmod m can be pushed back to aA⌈m⌉−B≡b(modm)a^{A\left \lceil \sqrt m \right \rceil -B} \equiv b \pmod m. The latter formula is obtained by dividing the left and right sides of the front formula by aBa^B, so there must be aB⊥ma^B \perp m, that is, a⊥ma\perp m.

Extended BSGS Algorithm

For a,b,m∈Z+a,b,m\in\mathbf{Z}^+, solve

ax≡b(modm)a^x\equiv b\pmod m

Here a,ma,m are not necessarily coprime.

When (a,m)=1(a, m)=1, aa has an inverse element modulo mm, so it can be solved using the BSGS algorithm. So we tried to find ways to make them mutually prime.

Specifically, let d1=(a,m)d_1=(a, m). If d1∤bd_1\nmid b, then the original equation has no solution. Otherwise we divide the equation by d1d_1 at the same time and get

ad1⋅ax−1≡bd1(modmd1)\frac{a}{d_1}\cdot a^{x-1}\equiv \frac{b}{d_1}\pmod{\frac{m}{d_1}}

If aa and md1\frac{m}{d_1} are still not mutually prime, divide again, let d2=(a,md1)d_2=\left(a, \frac{m}{d_1}\right). If d2∤bd1d_2\nmid \frac{b}{d_1}, the equation has no solution; otherwise divide by d2d_2 at the same time to get

a2d1d2⋅ax−2≡bd1d2(modmd1d2)\frac{a^2}{d_1d_2}\cdot a^{x-2}≡\frac{b}{d_1d_2} \pmod{\frac{m}{d_1d_2}}

In the same way, continue to judge in this way until a⊥md1d2⋯dka\perp \dfrac{m}{d_1d_2\cdots d_k}.

Remember D=∏i=1kdiD=\prod_{i=1}^kd_i, so the equation becomes like this:

akD⋅ax−k≡bD(modmD)\frac{a^k}{D}\cdot a^{x-k}\equiv\frac{b}{D} \pmod{\frac{m}{D}}

Since a⊥mDa\perp\dfrac{m}{D}, akD⊥mD\dfrac{a^k}{D}\perp \dfrac{m}{D} follows. In this way, akD\dfrac{a^k}{D} has an inverse element, so move it to the right-hand side. This is an ordinary BSGS problem. So after solving x−kx-k and adding kk, it is the solution of the original equation.

Note that it is not excluded that the solution is less than or equal to kk, so before eliminating factors, do the Θ(k)\Theta(k) enumeration and directly verify ai≡b(modm)a^i\equiv b \pmod m, so that this situation can be avoided.

Fast Discrete Logarithm with Range Preprocessing

The time complexity of the previous BSGS algorithm is a single O(m)O(\sqrt m), and the efficiency is low when the query magnitude is large. If the modulus solved each time is a fixed prime number pp, we have a fast algorithm based on range preprocessing.

We already know ind⁡g(ab)≡ind⁡ga+ind⁡gb(modp−1)\operatorname{ind}_g(ab)\equiv\operatorname{ind}_g a+\operatorname{ind}_g b\pmod{p-1}, so we can only calculate the discrete logarithm of all prime numbers through the BSGS algorithm. The discrete logarithm of the composite number can be converted into the sum of the discrete logarithms of several known prime numbers through this formula. At this time, the complexity is still not optimal. We consider preprocessing only a part of the discrete logarithms. Specifically, we preprocess the discrete logarithms from 11 to L=⌊p⌋+1L = \lfloor\sqrt p\rfloor + 1. Note that the BSGS block length BB at this time cannot be taken O(L)O(\sqrt{L}), because the complexity of the BSGS preprocessing (insertion into the hash table) part is O(B)O(B), and the query requires a total of O(π(L))O(\pi(L)) times, so the total time complexity is O(B+π(L)pB)O\left(B+\dfrac{\pi(L)p}{B}\right), and B=O(π(L)p)B=O(\sqrt{\pi(L)p}) is optimal at this time. Using the prime number theorem to π(n)∼nlog⁡n\pi(n)\sim\dfrac{n}{\log n}, the total preprocessing time complexity can be balanced to O(p3/4log⁡1/2p)O\left(\dfrac{p^{3/4}}{\log^{1/2} p}\right).

Next is how to find the answer. Assume that the current requirement is ind⁡gy\operatorname{ind}_g y, if y≤Ly\le L, return directly, otherwise set p=vy+rp=vy+r, then v=⌊py⌋<Lv=\left\lfloor\dfrac{p}{y}\right\rfloor<L, r=p mod yr=p\bmod y, y=p−rvy=\dfrac{p-r}{v}, thus

ind⁡gy≡ind⁡g(p−r)−ind⁡gv≡ind⁡g(−r)−ind⁡gv≡ind⁡g(p−1)+ind⁡gr−ind⁡gv(modp−1).\begin{aligned} \operatorname{ind}_g y &\equiv \operatorname{ind}_g (p-r)-\operatorname{ind}_g v\\ &\equiv \operatorname{ind}_g (-r)-\operatorname{ind}_g v\\ &\equiv \operatorname{ind}_g (p-1)+\operatorname{ind}_g r-\operatorname{ind}_g v \pmod{p-1}. \end{aligned}

Note that ind⁡g(p−1)=(p−1)/2\operatorname{ind}_g (p-1)=(p-1)/2, so we only need to recursively calculate the discrete logarithm of rr.

We can also consider another expression of yy, noting p=vy+r=(v+1)y+r−yp=vy+r=(v+1)y+r-y, then y=p−r+yv+1y=\dfrac{p-r+y}{v+1}, thus

ind⁡gy≡ind⁡g(y−r)−ind⁡g(v+1)(modp−1).\operatorname{ind}_g y\equiv \operatorname{ind}_g (y-r)-\operatorname{ind}_g (v+1) \pmod{p-1}.

We have v+1≤Lv+1 \le L, so we only need to recursively calculate the discrete logarithm of y−ry-r.

Combining these two calculation methods, we have min⁡{r,y−r}≤y2\min\{r,y-r\}\le \dfrac{y}{2}, so the smaller recursive calculation can achieve the query complexity of O(log⁡p)O(\log p).

So far we have obtained an algorithm with a time complexity of O(p3/4log⁡1/2p)−O(log⁡p)O\left(\dfrac{p^{3/4}}{\log^{1/2} p}\right)-O(\log p).

Luogu11175 [Template] Fast discrete logarithm based on range preprocessing
#include <cmath>
#include <iostream>
#include <unordered_map>
using namespace std;

const int N = 1'000'000;  // sqrt(P * sqrt(P) / ln(P))

unordered_map<int, int> M;
int Lg[N], p[N], t;
bool vis[N];
int P, g, B, sq, LP_1, inv;

int fadd(int x, int y, int P) {
  x += y;
  if (x >= P) x -= P;
  return x;
}

int fsub(int x, int y, int P) {
  x -= y;
  if (x < 0) x += P;
  return x;
}

int fmul(int x, int y, int P) { return 1ll * x * y % P; }

int qpow(int x, int y) {
  int ans = 1;
  while (y) {
    if (y & 1) ans = fmul(ans, x, P);
    x = fmul(x, x, P);
    y >>= 1;
  }
  return ans;
}

int calc(int x) {
  int s = x;
  for (int i = 0; i <= P / B; ++i) {
    if (M.find(s) != M.end()) return i * B + M[s];
    s = fmul(s, inv, P);
  }
  return -1;
}

void init() {
  int s = 1;
  for (int i = 0; i < B; ++i) {
    if (M.find(s) != M.end()) break;
    M[s] = i, s = fmul(s, g, P);
  }
  inv = qpow(qpow(g, B), P - 2);
  for (int i = 2; i <= sq; ++i) {
    if (!vis[i]) {
      p[++t] = i;
      Lg[i] = calc(i);
    }
    for (int j = 1; j <= t && p[j] * i <= sq; ++j) {
      vis[p[j] * i] = true;
      Lg[p[j] * i] = fadd(Lg[p[j]], Lg[i], P - 1);
      if (i % p[j] == 0) break;
    }
  }
}

int solve(int y) {
  if (y <= sq) return Lg[y];
  int v = P / y, r = P % y;
  if (r < y - r) return fadd(fsub(solve(r), Lg[v], P - 1), LP_1, P - 1);
  return fsub(solve(y - r), Lg[v + 1], P - 1);
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cin >> P >> g;
  sq = sqrt(P) + 1;
  B = sqrt(1ll * P * sqrt(P) / log(P));
  init();
  LP_1 = (P - 1) / 2;  // g ^ LP_1 = P - 1 (mod P)
  int T;
  cin >> T;
  while (T--) {
    int x;
    cin >> x;
    cout << solve(x) << '\n';
  }
  return 0;
}

Exercises

**Part of the content and code of this page are adapted from the blog post Дискретное извлечение корня and its English-language edition Discrete Root. The copyright agreement for the Russian version is Public Domain + Leave a Link; the copyright agreement for the English version is CC-BY-SA 4.0. **

References

  1. Discrete logarithm - Wikipedia
  2. Pan Chengdong, Pan Chengbiao. Elementary number theory.
  3. Feng Keqin. Elementary number theory and its applications.

This content comes from OI Wiki: Discrete Logarithm. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.