Purpose: Compute a modular square root — find with for an odd prime — in expected time.

Algorithm

  1. Check solvability with the Euler criterion: , otherwise no root exists.
  2. Easy case : the answer is . Done in one modpow.
  3. General case. Write with odd.
  4. Find any quadratic non-residue (try ; half of all residues work, so this takes tries on average). Set .
  5. Initialise , , .
  6. While :
    • find the least with ;
    • set , then , , , .
  7. Return (and , the other root).

Code

long long tonelli(long long a, long long p) {
    a %= p; if (a < 0) a += p;
    if (a == 0) return 0;
    if (powmod(a, (p - 1) / 2, p) != 1) return -1;      // not a residue
    if (p % 4 == 3) return powmod(a, (p + 1) / 4, p);
 
    long long q = p - 1; int s = 0;
    while (q % 2 == 0) { q /= 2; s++; }
 
    long long z = 2;
    while (powmod(z, (p - 1) / 2, p) != p - 1) z++;      // find a non-residue
 
    long long c = powmod(z, q, p);
    long long x = powmod(a, (q + 1) / 2, p);
    long long t = powmod(a, q, p);
    int m = s;
 
    while (t != 1) {
        int i = 0;
        long long tt = t;
        while (tt != 1) { tt = tt * tt % p; i++; }
        long long b = powmod(c, 1LL << (m - i - 1), p);
        x = x * b % p;
        c = b * b % p;
        t = t * c % p;
        m = i;
    }
    return x;
}

Paradigm

Group-theoretic descent. Each iteration halves the order of inside the 2-Sylow subgroup of , so the loop terminates in at most rounds.

Complexity

  • Time: expected — for the non-residue search (constant tries × one modpow) plus for the descent
  • Space:

Why It Works

Throughout the loop the invariant holds, and lies in the subgroup of order . The value is the exact order of (as a power of two), and is chosen so that has the same order as but is its inverse there — multiplying kills the top bit. Since strictly decreases, we reach , at which point the invariant reads . ∎

Variants / Use Cases