The Tonelli-Shanks Algorithm, Explained
Modular arithmetic — perhaps surprisingly — has tons of practical applications, ranging from hashing strings to forming cryptosystems. And — maybe not so surprisingly — these involve solving equations which would normally be trivial in real numbers, but aren't nearly as simple in the discrete case. Such is the case with , aka finding the square root of . That is exactly where the Tonelli-Shanks algorithm comes in. It solves in — pardon my math — any cyclic group of even order, in group operations.
Sadly, the way it's often presented is convoluted both in terms of the pseudocode provided and the actual reasoning behind its correctness. As such, this article is my attempt at making sense of Tonelli-Shanks, in words myself from two years ago would understand.
The basics
I'll try not to overload the reader with too much mathematical notation and formalisms. That said, we do first need to introduce some basics. Feel free to skip over what you already know.
Modular arithmetic
Throughout this article, we'll be working with integers modulo a prime . In simple terms, this means that after every operation we divide the result by and leave just the remainder, effectively limiting ourselves to the numbers . For example, . That last part just means we did this taking . From now on I'll omit the special symbols and write where is known from the context. Furthermore, in our example , that is negative numbers get reduced as well.
An important property of multiplication modulo is that for any non-zero and , it holds that . This is somewhat crucial, so we will assume the number in is never zero. We're not losing out on much anyway since the only solution to is zero itself.
Generators and square roots
It turns out there always exists a number such that if we keep multiplying it by itself (aka take ), we will eventually get all the numbers . This is called a generator.
It follows from the above that every number from can be written as for some in . Additionally, the number of elements in is which is even, unless (again, a trivial case: and ).
And these powers of cycle around. That is, becomes again, becomes , etc. So the exponents sort of act like numbers modulo . An important realization stems from this fact: only for even have square roots. This is because if is odd, then were any integer to satisfy , it would mean that , which can't be true since and are even, and is not. We call these numbers quadratic residues, in contrast with quadratic nonresidues which do not have a square root.
Oh, and one more thing. You know how in real numbers ? Well if you think about it, is a perfectly valid solution too. We sort of arbitrarily choose the positive one. Among (non-zero) integers modulo , every quadratic residue also has exactly two square roots, each being the negative of the other. Our algorithm will find one of them, and it's not really ever clear which one. If you badly need consistency, simply choosing the smaller one is an option.
Fast exponentiation
One last thing we'll need is an efficient way to calculate modulo . This is accomplished using a simple recursive algorithm often referred to as fast exponentiation or square-and-multiply:
If , return
If is odd, return
Otherwise, set and return
Clearly, this yields the correct value of . And because every (at most) two iterations the value of halves, the runtime of this algorithm is .
The Tonelli-Shanks algorithm
With the preliminaries out of the way, let's get down to business. The Tonelli-Shanks algorithm is as follows:
Let be an odd prime. We will solve for a quadratic residue .
First, we somehow find a quadratic nonresidue .
We will keep two numbers and , such that at all times.
Also, will always stay strictly "more even" than , that is if some divides then divides .
To start out, and .
As long as is even:
If , set and .
Otherwise, set and .
The solution to is .
So why on earth does this work? Let's go through the algorithm step by step.
Finding the nonresidue
For Tonelli-Shanks to work, we need a quadratic nonresidue . Thankfully, finding one is easy. We can make use of a theorem called Euler's criterion:
If then is a quadratic residue.
Otherwise, and is a nonresidue.
I won't show it here, but the proof is not that elaborate and you can easily find it online.
So we can check if any given is good in using the fast exponentiation algorithm described above. And as it turns out, we can just keep choosing a random until one passes our test. This is because exactly half of the numbers in are nonresidues, so the expected number of times we'll have to repeat this is . And again, I'll leave the proof as an exercise to the reader.
In total, this part of the algorithm takes time.
Initial state
We start with and , which as we can see satisfies our invariants:
And obviously, is divisible by one less than .
The loop
First of all, if then we clearly can use these new values for and , keeping both our invariants.
The second case is more interesting. We can't just do the same thing, because which would break one of our invariants. But, is currently true. And if we realize that the only square roots of are and (as there can only be two), then we know , since dividing the exponent by two is exactly what taking a square root is. So if we multiply this number by another , we will get again. And this is what adding to does, as per Euler's criterion:
How about the other invariant? Can we be sure that adding won't cause the new to be "less even" than ? Yes, because both and are multiples of , so their sum is still divisible by at least one more than .
The result
The final answer looks just as mysterious. But you can verify it's correct by simply taking its square:
Runtime complexity
This part is relatively simple. Each iteration involves two fast exponentiations, and because after each iteration becomes , there can't be more than iterations before all the twos get divided out. In total, this nets us a algorithm.
There is also the initial search for , which is the only randomized part of Tonelli-Shanks. But unless we get hugely unlucky this will take a comparable amount of time, and for any given we only need to find once.
Finishing thoughts
That's it! As you can (hopefully) see, there is no magic involved. I wouldn't go as far as to call this algorithm intuitive, but you can surely make sense of it, even without any advanced math knowledge. And the implementation isn't hard either. Let's end with coding one up in Python:
import random
# These work for prime p > 2.
# Python's 3-argument pow performs fast exponentiation mod p.
def tonelli_shanks(a, z, p):
s = (p - 1) // 2
t = p - 1
while s % 2 == 0:
apow = pow(a, s // 2, p)
zpow = pow(z, t // 2, p)
azpow = apow * zpow % p
if azpow == 1:
s = s // 2
t = t // 2
else:
s = s // 2
t = t // 2 + (p - 1) // 2
apow = pow(a, (s + 1) // 2, p)
zpow = pow(z, t // 2, p)
return apow * zpow % p
def is_nonresidue(x, p):
return pow(x, (p - 1) // 2, p) == p - 1
def find_nonresidue(p):
while True:
z = random.randint(2, p - 1)
if is_nonresidue(z, p):
return z
Special thanks to tfkls for helpful feedback, as well as to dr. Lech Duraj for the original explanation given during his lecture.