Back to Number Theory Dsa

Tonelli-Shanks & Cipolla Compared

September 22, 2026 Wasil Zafar 9 min read

Compare two constructive square-root algorithms over prime fields.

Table of Contents

  1. Intuition, Vocabulary and Foundations
  2. Step-by-Step Derivations and Algorithms
  3. Implementation, Complexity and Engineering
  4. Applications, Case Studies and Exercises

Intuition, Vocabulary and Foundations

Finding a modular square root means reversing a square on a finite clock. Modulo $13$, both $5$ and $8$ square to $12$, so $x^2\equiv12\pmod{13}$ has two roots. In an odd prime field, every nonzero square has exactly the pair $x$ and $-x$, while a nonresidue has no root.

Euler's criterion tests the domain before construction:

$$ a^{(p-1)/2}\equiv \begin{cases} 1 \pmod p,& a\text{ is a nonzero quadratic residue},\\ -1 \pmod p,& a\text{ is a quadratic nonresidue}. \end{cases} $$

Tonelli–Shanks writes $p-1=q2^s$, where $q$ is odd, and removes the remaining powers of two from an exponentiation-based candidate. Cipolla instead chooses $t$ such that $w=t^2-a$ is a nonresidue and computes in the quadratic extension $\mathbb F_p[u]/(u^2-w)$. Both methods construct an element whose square is $a$, but they organize the work differently.

ConceptExact exampleNon-example or boundaryQuestion answered
Quadratic residue$12$ modulo $13$, because $5^2\equiv12$$2$ modulo $13$ is a nonresidueDoes $x^2\equiv a\pmod p$ have a solution?
Two-adic decomposition$40=5\cdot2^3$, so $q=5$ and $s=3$ for $p=41$$40=10\cdot2^2$ leaves an even odd-part candidateWhat power-of-two subgroup must Tonelli–Shanks resolve?
Quadratic nonresidue$2$ modulo $13$, since $2^6\equiv-1$$3$ modulo $13$ is a residue because $4^2\equiv3$Which element supplies the required group structure?
Quadratic extensionIf $w$ is a nonresidue, adjoining $u$ with $u^2=w$ forms $\mathbb F_{p^2}$If $w$ is a residue, $X^2-w$ splitsWhere does Cipolla perform exponentiation?
Root multiplicity$5$ and $8$ are the roots of $12$ modulo $13$Reporting only $5$ hides the second rootHow should a solver expose all roots?
Prime-field assumptionBoth algorithms apply for odd prime $p$Composite moduli can have more than two rootsWhich domain makes the proofs valid?

Step-by-Step Derivations and Algorithms

Reduce $a$ modulo $p$. The cases $p=2$ and $a=0$ are immediate. For odd prime $p$, reject $a$ when Euler's criterion is not $1$. When $p\equiv3\pmod4$, the direct candidate

$$r=a^{(p+1)/4}\pmod p$$

works because $r^2=a^{(p+1)/2}\equiv a$.

For Tonelli–Shanks, decompose $p-1=q2^s$ with odd $q$, find a nonresidue $z$, and initialize

$$c=z^q,\qquad r=a^{(q+1)/2},\qquad t=a^q,\qquad m=s.$$

The invariant is $r^2\equiv at\pmod p$, while $t$ has order dividing $2^m$. If $t\ne1$, find the least $i$ such that $t^{2^i}=1$, choose $b=c^{2^{m-i-1}}$, and update $r\leftarrow rb$, $t\leftarrow tb^2$, $c\leftarrow b^2$, and $m\leftarrow i$. Since $m$ strictly decreases, termination gives $t=1$ and $r^2\equiv a$.

Cipolla chooses $t\in\mathbb F_p$ until $w=t^2-a$ is a nonresidue. Represent $x+yu$ as $(x,y)$, with $u^2=w$, and multiply by

$$(x_1,y_1)(x_2,y_2)=(x_1x_2+y_1y_2w,\;x_1y_2+x_2y_1).$$

Frobenius conjugation sends $t+u$ to $t-u$, so $(t+u)^{(p+1)/2}$ lies in the base field and its first coordinate squares to $a$.

Tonelli-Shanks & Cipolla Compared algorithm map
flowchart TD
    A[Normalize a and validate prime p] --> B{Trivial or nonresidue?}
    B -->|Trivial| C[Return immediate roots]
    B -->|Nonresidue| D[Return no roots]
    B -->|Residue| E{Easy exponent case?}
    E -->|Yes| F[Use direct exponent]
    E -->|No| G{Choose algorithm}
    G -->|Tonelli-Shanks| H[Reduce two-power order]
    G -->|Cipolla| I[Exponentiate in extension]
    F --> J[Verify and return both roots]
    H --> J
    I --> J

Tonelli–Shanks preserves $r^2\equiv at\pmod p$ and strictly lowers the two-power order bound of $t$; Cipolla performs binary exponentiation in a correctly constructed field of $p^2$ elements. With field multiplication treated as unit cost, Cipolla uses $O(\log p)$ operations, while basic Tonelli–Shanks uses $O(\log p+s^2)$ operations for $p-1=q2^s$; both need $O(1)$ auxiliary field elements.

Implementation, Complexity and Engineering

A robust API validates that $p$ is prime, normalizes negative operands, treats $p=2$ separately, returns both roots in sorted order, and verifies every candidate. The self-contained implementation uses trial division to enforce the prime-domain contract; production code should pair these routines with a deterministic machine-word primality checker.

Cipolla's search is deterministic here for reproducible tests. A randomized search has an expected constant number of trials because about half of the nonzero field elements are nonresidues. Native fixed-width implementations must also ensure that multiplication is reduced without overflow.

from math import isqrt


def is_prime(n):
    if not isinstance(n, int) or isinstance(n, bool) or n < 2:
        return False
    if n % 2 == 0:
        return n == 2
    divisor = 3
    while divisor <= isqrt(n):
        if n % divisor == 0:
            return False
        divisor += 2
    return True


def legendre(a, p):
    a %= p
    if a == 0:
        return 0
    value = pow(a, (p - 1) // 2, p)
    return -1 if value == p - 1 else value


def verified_roots(a, p, root):
    a %= p
    root %= p
    if root * root % p != a:
        raise ArithmeticError("square-root certificate failed")
    return tuple(sorted({root, (-root) % p}))


def tonelli_shanks(a, p):
    if not is_prime(p):
        raise ValueError("p must be prime")
    a %= p
    if p == 2:
        return (a,)
    if a == 0:
        return (0,)
    if legendre(a, p) != 1:
        return ()
    if p % 4 == 3:
        return verified_roots(a, p, pow(a, (p + 1) // 4, p))

    q_value, s_value = p - 1, 0
    while q_value % 2 == 0:
        q_value //= 2
        s_value += 1
    z_value = 2
    while legendre(z_value, p) != -1:
        z_value += 1

    m_value = s_value
    c_value = pow(z_value, q_value, p)
    t_value = pow(a, q_value, p)
    r_value = pow(a, (q_value + 1) // 2, p)
    while t_value != 1:
        i_value, power = 0, t_value
        while power != 1 and i_value < m_value:
            power = power * power % p
            i_value += 1
        if i_value == m_value:
            raise ArithmeticError("Tonelli-Shanks invariant failed")
        b_value = pow(c_value, 1 << (m_value - i_value - 1), p)
        r_value = r_value * b_value % p
        b_squared = b_value * b_value % p
        t_value = t_value * b_squared % p
        c_value, m_value = b_squared, i_value
    return verified_roots(a, p, r_value)


def extension_multiply(left, right, w_value, p):
    x1, y1 = left
    x2, y2 = right
    return ((x1 * x2 + y1 * y2 * w_value) % p,
            (x1 * y2 + x2 * y1) % p)


def extension_power(base, exponent, w_value, p):
    result = (1, 0)
    while exponent:
        if exponent & 1:
            result = extension_multiply(result, base, w_value, p)
        base = extension_multiply(base, base, w_value, p)
        exponent >>= 1
    return result


def cipolla(a, p):
    if not is_prime(p):
        raise ValueError("p must be prime")
    a %= p
    if p == 2:
        return (a,)
    if a == 0:
        return (0,)
    if legendre(a, p) != 1:
        return ()
    t_value = 0
    while True:
        w_value = (t_value * t_value - a) % p
        if legendre(w_value, p) == -1:
            break
        t_value += 1
    root, extension_part = extension_power(
        (t_value, 1), (p + 1) // 2, w_value, p
    )
    if extension_part != 0:
        raise ArithmeticError("result did not return to base field")
    return verified_roots(a, p, root)


assert tonelli_shanks(12, 13) == (5, 8)
assert cipolla(12, 13) == (5, 8)
assert tonelli_shanks(-1, 5) == (2, 3)
assert cipolla(-1, 5) == (2, 3)
assert tonelli_shanks(0, 17) == cipolla(0, 17) == (0,)
assert tonelli_shanks(7, 2) == cipolla(7, 2) == (1,)
assert tonelli_shanks(2, 13) == cipolla(2, 13) == ()

small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43]
for prime in small_primes:
    for a_value in range(prime):
        expected = tuple(x for x in range(prime) if x * x % prime == a_value)
        assert tonelli_shanks(a_value, prime) == expected
        assert cipolla(a_value, prime) == expected

for composite in (1, 4, 9, 15, 21):
    for algorithm in (tonelli_shanks, cipolla):
        try:
            algorithm(1, composite)
        except ValueError:
            pass
        else:
            raise AssertionError("composite modulus was accepted")

print("all tests passed")
Method or caseWork and storageMain failure modeMitigation
Direct exponent for $p\equiv3\pmod4$$O(\log p)$ multiplications and $O(1)$ field elementsApplying the formula in another congruence classDispatch only after checking $p\bmod4$
Tonelli–ShanksExponentiation plus at most $s-1$ correctionsIncorrect least-$i$ search or update orderPreserve $r^2\equiv at$ and exhaustively test small fields
CipollaExpected constant search trials and $O(\log p)$ extension multiplicationsChoosing a residue for $w$Require the Legendre symbol of $w$ to be $-1$
Random nonresidue searchExpected about two trialsNondeterministic tests or weak randomnessInject the generator or use deterministic search in tests
Native-width multiplicationFast when products fitOverflow before reductionUse widened products or an overflow-safe reduction backend
Composite modulusOutside both proofsWrong multiplicity or invalid inversesReject it or solve prime-power cases and combine with CRT

Applications, Case Studies and Exercises

For a transparent trace, solve $x^2\equiv5\pmod{41}$. Since $40=5\cdot2^3$, Tonelli–Shanks exposes multiple correction steps and returns $13$ and $28$. Cipolla reaches the same pair through extension-field exponentiation, providing an independently structured check.

In a production-shaped validation harness, enumerate every residue for small primes and compare both algorithms with brute force. For larger primes, generate random $x$, set $a=x^2\bmod p$, run both algorithms, and require equal root sets plus direct squaring certificates. Include nonresidues, $a=0$, $p=2$, negative inputs, varied values of $v_2(p-1)$, and rejected composite moduli.

Practice

Apply Tonelli-Shanks & Cipolla Compared

Case study 1 — worked trace. Use $a=5$ and $p=41$. Check Euler's criterion, derive $p-1=5\cdot2^3$, trace every Tonelli–Shanks state $(m,c,t,r)$, and independently trace Cipolla's selected $t$, nonresidue $w$, and extension exponentiation. Success means both methods return exactly $\{13,28\}$ and both roots square to $5$.

Case study 2 — differential testing. For every prime $p\le200$ and every $0\le a\lt p$, compare Tonelli–Shanks, Cipolla, and brute force. Then choose several larger primes, generate 1,000 residues from random roots, and cross-check both algorithms. Success means there are no disagreements and every root verifies.

Tiered exercises. Recall: state Euler's criterion and both root-count cases. Derivation: prove the Tonelli–Shanks invariant and Cipolla multiplication rule. Implementation: inject a randomized nonresidue source. Debugging: catch code that returns one root or mishandles $p=2$. Extension: benchmark both algorithms by $v_2(p-1)$ and explain their practical crossover.

Use the direct exponent in easy congruence classes, Tonelli–Shanks when the two-adic structure is favorable, and Cipolla when uniform extension-field exponentiation is attractive. Never apply prime-field algorithms to composite moduli or trust an unverified candidate; the next step is lifting roots to prime powers and combining them with CRT.