Solving Discrete Logarithms with BSGS and Extensions

BSGS Algorithm

The Baby-step Giant-step (BSGS) algorithm solves discrete logarithm problems of the form \(a^x \equiv n \pmod{p}\) for the smallest non-negative integer \(x\), where \(a\) and \(p\) are coprime constants.

Algorithm Overview

Express \(x\) as \(x = A \cdot \lceil \sqrt{p} \rceil - B\) where \(0 \leq A, B \leq \lceil \sqrt{p} \rceil\). This transforms the equation to:

\[a^{A \cdot \lceil \sqrt{p} \rceil} \equiv n \cdot a^B \pmod{p}\]

Precompute all possible \(n \cdot a^B\) values and store them in a hash table with their corresponding \(B\) values. Then iterate over possible \(A\) values to find a matching entry. The solution is given by \(x = A \cdot \lceil \sqrt{p} \rceil - B\). Time complexity is \(O(\sqrt{p})\), or \(O(\sqrt{p} \log p)\) with a std::map.

Implementation

#include <iostream>
#include <cmath>
#include <map>
using namespace std;
typedef long long ll;

ll power_mod(ll base, ll exp, ll mod) {
    ll result = 1;
    while (exp) {
        if (exp & 1) result = (result * base) % mod;
        base = (base * base) % mod;
        exp >>= 1;
    }
    return result;
}

ll bsgs(ll base, ll target, ll prime) {
    base %= prime, target %= prime;
    if (target == 1) return 0;
    if (base == 0) return (target == 0 ? 1 : -1);

    map<ll, ll> index_map;
    ll step_size = ceil(sqrt(prime));
    ll current = target;
    
    for (ll j = 0; j <= step_size; j++) {
        index_map[current] = j;
        current = (current * base) % prime;
    }

    ll base_step = power_mod(base, step_size, prime);
    current = base_step;
    for (ll i = 1; i <= step_size; i++) {
        if (index_map.count(current) && (i * step_size - index_map[current] >= 0))
            return i * step_size - index_map[current];
        current = (current * base_step) % prime;
    }
    return -1;
}

int main() {
    ll p, a, n;
    cin >> p >> a >> n;
    ll result = bsgs(a, n, p);
    if (result != -1) cout << result << endl;
    else cout << "no solution" << endl;
    return 0;
}

Extended BSGS

Extended BSGS (exBSGS) solves \(a^x \equiv n \pmod{p}\) when \(a\) and \(p\) are not necessarily coprime.

Algorithm Overview

Repeatedly divide the equation by \(d = \gcd(a, p)\) until \(a\) and \(p\) become coprime. After \(k\) iterations, the equation transforms to:

\[a^{x-k} \cdot \frac{a^k}{\prod d} \equiv \frac{n}{\prod d} \pmod{\frac{p}{\prod d}}\]

Solve the transformed equation using BSGS and add \(k\) to the solution.

Implementation

#include <iostream>
#include <map>
using namespace std;
typedef long long ll;

ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }

ll bsgs(ll base, ll target, ll prime, ll coeff);

ll exbsgs(ll base, ll target, ll prime) {
    base %= prime, target %= prime;
    if (target == 1 || prime == 1) return 0;
    if (base == 0) return (target == 0 ? 1 : -1);

    ll iter_count = 0, accum = 1;
    while (true) {
        ll d = gcd(base, prime);
        if (d == 1) break;
        if (target % d != 0) return -1;
        iter_count++;
        prime /= d, target /= d;
        accum = (accum * (base / d)) % prime;
        if (accum == target) return iter_count;
    }
    ll result = bsgs(base, target, prime, accum);
    return (result == -1) ? -1 : result + iter_count;
}

int main() {
    ll a, p, n;
    while (cin >> a >> p >> n && a) {
        ll result = exbsgs(a, n, p);
        if (result != -1) cout << result << endl;
        else cout << "No Solution" << endl;
    }
    return 0;
}

Matrix BSGS

Matrix BSGS solves \(A^x \equiv B \pmod{p}\) for matrices \(A\) and \(B\), using matrix multiplication and hashing.

Implementation

#include <iostream>
#include <cmath>
#include <unordered_map>
using namespace std;

typedef unsigned long long ull;
const int MATRIX_SIZE = 70;
const ull HASH_BASE = 998244353;

struct Matrix {
    int size;
    int data[MATRIX_SIZE][MATRIX_SIZE];
    
    Matrix(int n = 0) : size(n) { 
        for (int i = 0; i < MATRIX_SIZE; i++)
            fill(data[i], data[i] + MATRIX_SIZE, 0);
    }

    ull hash_value() {
        ull val = 0;
        for (int i = 0; i < size; i++)
            for (int j = 0; j < size; j++)
                val = val * HASH_BASE + data[i][j];
        return val;
    }

    Matrix operator*(const Matrix& other) {
        Matrix res(size);
        for (int i = 0; i < size; i++)
            for (int k = 0; k < size; k++)
                for (int j = 0; j < size; j++)
                    res.data[i][j] = (res.data[i][j] + data[i][k] * other.data[k][j]) % mod;
        return res;
    }
};

int matrix_bsgs(Matrix base, Matrix target, int mod) {
    if (target.is_identity()) return 0;
    int step = sqrt(mod) + 1;
    Matrix current = target;
    unordered_map<ull, int> hash_map;
    Matrix base_step(base.size);
    base_step.set_identity();

    for (int j = 0; j < step; j++) {
        hash_map[current.hash_value()] = j;
        current = base * current;
        base_step = base_step * base;
    }

    Matrix temp = base_step;
    for (int i = 1; i <= step; i++) {
        ull hash_val = temp.hash_value();
        if (hash_map.count(hash_val)) return i * step - hash_map[hash_val];
        temp = base_step * temp;
    }
    return -1;
}

Tags: BSGS exBSGS discrete-logarithm modular-arithmetic matrix-exponentiation

Posted on Wed, 04 Nov 2026 16:23:25 +0000 by stylusrose