FLINT3.7.0-dev
Algorithm

Discrete logarithms modulo a prime (Pohlig–Hellman)

Cost
For p - 1 = \prod q_i^{e_i}, one call costs O(\sum_i e_i (\log p + D_i)) multiplications modulo p, where D_i is about \sqrt{q_i} for q_i up to about 10^4 and between q_i / 100 and q_i / 50 for larger q_i.

Let p be prime and let g be a primitive root modulo p, so that every y \in (\mathbb{Z}/p\mathbb{Z})^\times is g^x for a unique x \in [0, p - 1), the discrete logarithm of y. For a general p no fast algorithm is known. When the group order p - 1 factors into small primes, the method of Pohlig and Hellman [PohHel1978] reduces the problem to logarithms in groups of prime order, which are solved by Shanks' baby-step giant-step method [Sha1971]. See also [CraPom2005].

FLINT implements this method in the nmod_discrete_log_pohlig_hellman functions.

Reduction to prime-power order#

Write p - 1 = \prod_{i=1}^{k} q_i^{e_i} with distinct primes q_i, and let c_i = (p-1)/q_i^{e_i}. The element h_i = g^{c_i} has order exactly q_i^{e_i}, and y^{c_i} = h_i^{x}. So x_i = x \bmod q_i^{e_i} is the logarithm of y^{c_i} to the base h_i, a problem in a group of order q_i^{e_i}. The x_i determine x by the Chinese remainder theorem:

x \equiv \sum_{i=1}^{k} x_i \, \epsilon_i \pmod{p - 1},

where the idempotent \epsilon_i = c_i \cdot (c_i^{-1} \bmod q_i^{e_i}) satisfies \epsilon_i \equiv 1 \pmod{q_i^{e_i}} and \epsilon_i \equiv 0 \pmod{q_j^{e_j}} for j \ne i.

Digits in a prime-power group#

Fix i and write q = q_i, e = e_i, h = h_i and x_i = \sum_{j=0}^{e-1} d_j q^j with 0 \le d_j < q. The element \gamma = h^{q^{e-1}} = g^{(p-1)/q} has order q.

Algorithm 1: Digits of the logarithm

Input: z = y^{c} with c = (p-1)/q^e, the elements h^{-1} and \gamma.

Output: x_i = \log_h z \bmod q^e.

  1. \beta \gets h^{-1}, X \gets 0.
  2. For j = 0, 1, \ldots, e - 1:

    1. w \gets z^{q^{e-1-j}}.
    2. Find d_j \in [0, q) with \gamma^{d_j} = w.
    3. z \gets z \beta^{d_j}, \beta \gets \beta^{q}, X \gets X + d_j q^j.
  3. Return X.

Theorem 1. In Algorithm 1, step 2.2 always has a solution, it is the digit d_j of x_i, and the algorithm returns x_i.

Proof. We show that at the start of iteration j, \beta = h^{-q^j} and z = h^{x_i - X_j} with X_j = \sum_{l < j} d_l q^l. This holds for j = 0. Since q^j divides x_i - X_j, write x_i - X_j = q^j (d_j + q m). Then

w = h^{(x_i - X_j) q^{e-1-j}} = h^{q^{e-1} d_j} \, h^{q^e m} = \gamma^{d_j},

because h has order q^e. As \gamma has order q, the solution of step 2.2 is unique in [0, q) and equals d_j. Step 2.3 multiplies z by h^{-q^j d_j}, giving h^{x_i - X_{j+1}}, and replaces \beta by h^{-q^{j+1}}.

Logarithms of prime order#

Step 2.2 is a discrete logarithm in the group of order q generated by \gamma. FLINT uses a baby-step giant-step search with a precomputed table of bounded size.

For q = 2 no search is needed: \gamma = -1, and d_j = 0 if w = 1, d_j = 1 if w = \gamma.

For odd q, choose a stride m and let C = \lceil q / m \rceil. The table holds the pairs (\gamma^{cm}, cm) for 0 \le c < C, sorted by the first entry. Any d \in [0, q) can be written d = cm + d' with 0 \le c < C and 0 \le d' < m, so \gamma^{d} = w if and only if w \gamma^{-d'} = \gamma^{cm}.

Algorithm 2: Table search

Input: w in the group generated by \gamma, the sorted table, and \gamma^{-1}.

Output: d with \gamma^{d} = w.

  1. For d' = 0, 1, \ldots, m - 1:

    1. If w = \gamma^{cm} for some table entry (found by binary search), return d = cm + d'.
    2. w \gets w \gamma^{-1}.
  2. Fail.

The search returns the correct digit. Let d \in [0, q) be the true logarithm of the input w. For d' \le d, the current value is \gamma^{d - d'} with 0 \le d - d' < q. Since 0 \le cm < q for every table entry, it matches an entry exactly when d - d' = cm. The first such d' is d \bmod m, with c = \lfloor d/m \rfloor < C.

The table costs C entries and the search at most m multiplications and m binary searches. Balancing the two gives m \approx \sqrt q. FLINT starts from m = \lceil \sqrt q \rceil and doubles m until C \le 100. So the table has at most 100 entries, and the search costs O(\sqrt q) multiplications for q up to about 10^4, but between q/100 and q/50 for larger q. This keeps the precomputation small and favours primes p for which p - 1 is smooth, which is how the functions are used in FLINT.

Precomputation#

nmod_discrete_log_pohlig_hellman_precompute_prime performs the parts that depend only on p:

  1. Factor p - 1 with n_factor.
  2. For each q_i, compute c_i, \epsilon_i \bmod (p - 1) and q_i^{e_i - 1}.
  3. Take g as the least a \ge 1 with a^{(p-1)/q_i} \ne 1 for all i, that is, the least primitive root. If p is prime, one exists. If none is found below p, an exception is thrown.
  4. For each i, compute \gamma_i = g^{(p-1)/q_i}, \gamma_i^{-1}, h_i^{-1} = g^{-c_i}, the stride m_i and the sorted table.

It returns an estimate of the number of multiplications of one run: for each i, the cost of the exponentiation y^{c_i}, plus for each of the e_i digits the cost of the exponentiation in step 2.1 of Algorithm 1, m_i (1 + \ln C_i) for the search and 2 \ln q_i for the power of \beta.

The full algorithm#

Algorithm 3: Pohlig–Hellman

Input: the precomputed data for p, and 0 < y < p.

Output: x \in [0, p - 1) with g^x = y.

  1. For each i: compute z = y^{c_i} and x_i by Algorithm 1, using Algorithm 2 for each digit.
  2. Return \sum_i x_i \epsilon_i \bmod (p - 1).

The sum in step 2 is accumulated in three words and reduced once at the end. If a search in Algorithm 2 fails, or w \notin \{1, -1\} when q = 2, an exception is thrown. For an odd prime p this happens only for y = 0, which has no logarithm.

Theorem 2. For prime p and 0 < y < p, Algorithm 3 returns the discrete logarithm of y to the base g.

Proof. By Theorem 1, step 1 computes x_i = x \bmod q_i^{e_i}, where the search of Algorithm 2 succeeds because w lies in the group generated by \gamma_i. By the properties of the idempotents, \sum_i x_i \epsilon_i \equiv x_i \pmod{q_i^{e_i}} for every i, so it is congruent to x modulo p - 1.

Cost. Each digit needs an exponentiation by q^{e-1-j} < p, a search and an exponentiation of \beta, so the total is O(\sum_i e_i (\log p + m_i)) multiplications, plus one exponentiation y^{c_i} for each i. When all q_i are small, this is O(\log^2 p).

Use in FLINT#

Referenced from#

References#

  • [PohHel1978] S. C. Pohlig and M. E. Hellman, "An improved algorithm for computing logarithms over GF(p) and its cryptographic significance", IEEE Transactions on Information Theory 24 (1978), 106–110, https://doi.org/10.1109/TIT.1978.1055817
  • [Sha1971] D. Shanks, "Class number, a theory of factorization, and genera", Proc. Symp. Pure Math. 20, American Mathematical Society (1971), 415–440
  • [CraPom2005] R. Crandall and C. Pomerance, Prime Numbers: A Computational Perspective, second edition, Springer (2005).
Cite this page

The FLINT team, “Discrete logarithms modulo a prime (Pohlig–Hellman),” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/number-theory/discrete-log.html (generated 2026-10-20).