1+ """
2+ Tonelli-Shanks algorithm for modular square roots.
3+
4+ Given an odd prime modulus ``prime`` and an integer ``residue``, find an
5+ integer ``root`` such that ``root ** 2 ≡ residue (mod prime)``, or report that
6+ no square root exists.
7+
8+ The algorithm is efficient when ``prime ≡ 3 (mod 4)`` (a single
9+ exponentiation) and uses the full Tonelli-Shanks procedure for
10+ ``prime ≡ 1 (mod 4)``.
11+
12+ https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm
13+ """
14+
15+ from __future__ import annotations
16+
17+
18+ def legendre_symbol (residue : int , prime : int ) -> int :
19+ """
20+ Compute the Legendre symbol (residue / prime).
21+
22+ Returns 1 if residue is a quadratic residue modulo prime (and residue
23+ is not divisible by prime), -1 if it is a non-residue, and 0 if
24+ residue ≡ 0 (mod prime).
25+
26+ >>> legendre_symbol(2, 7)
27+ 1
28+ >>> legendre_symbol(3, 7)
29+ -1
30+ >>> legendre_symbol(14, 7)
31+ 0
32+ >>> legendre_symbol(5, 11)
33+ 1
34+ """
35+ if prime <= 2 or prime % 2 == 0 :
36+ raise ValueError ("prime must be an odd prime" )
37+ symbol = pow (residue % prime , (prime - 1 ) // 2 , prime )
38+ return - 1 if symbol == prime - 1 else symbol
39+
40+
41+ def tonelli_shanks (residue : int , prime : int ) -> int :
42+ """
43+ Return a modular square root of ``residue`` modulo odd prime ``prime``.
44+
45+ If both roots exist, the smaller non-negative representative is returned.
46+ Raises ValueError when ``residue`` is not a quadratic residue, or when
47+ ``prime`` is not a valid odd prime modulus for this routine.
48+
49+ >>> tonelli_shanks(5, 41)
50+ 13
51+ >>> pow(13, 2, 41)
52+ 5
53+ >>> tonelli_shanks(2, 7)
54+ 3
55+ >>> pow(3, 2, 7)
56+ 2
57+ >>> tonelli_shanks(10, 13)
58+ 6
59+ >>> tonelli_shanks(0, 11)
60+ 0
61+ >>> tonelli_shanks(8, 17)
62+ 5
63+ >>> pow(5, 2, 17)
64+ 8
65+ >>> tonelli_shanks(3, 7)
66+ Traceback (most recent call last):
67+ ...
68+ ValueError: 3 is not a quadratic residue modulo 7
69+ >>> tonelli_shanks(5, 4)
70+ Traceback (most recent call last):
71+ ...
72+ ValueError: prime must be an odd prime
73+ >>> tonelli_shanks(5, 1)
74+ Traceback (most recent call last):
75+ ...
76+ ValueError: prime must be an odd prime
77+ """
78+ if prime <= 2 or prime % 2 == 0 :
79+ raise ValueError ("prime must be an odd prime" )
80+
81+ residue %= prime
82+ if residue == 0 :
83+ return 0
84+
85+ symbol = legendre_symbol (residue , prime )
86+ if symbol != 1 :
87+ raise ValueError (f"{ residue } is not a quadratic residue modulo { prime } " )
88+
89+ # Fast path: prime ≡ 3 (mod 4)
90+ if prime % 4 == 3 :
91+ root = pow (residue , (prime + 1 ) // 4 , prime )
92+ return min (root , prime - root )
93+
94+ # Write prime - 1 = q * 2^s with q odd
95+ exponent_q = prime - 1
96+ power_of_two_s = 0
97+ while exponent_q % 2 == 0 :
98+ exponent_q //= 2
99+ power_of_two_s += 1
100+
101+ # Find a quadratic non-residue z
102+ non_residue = 2
103+ while legendre_symbol (non_residue , prime ) != - 1 :
104+ non_residue += 1
105+
106+ modular_c = pow (non_residue , exponent_q , prime )
107+ modular_r = pow (residue , (exponent_q + 1 ) // 2 , prime )
108+ modular_t = pow (residue , exponent_q , prime )
109+ remaining_s = power_of_two_s
110+
111+ while modular_t != 1 :
112+ # Find the least i such that t^(2^i) ≡ 1 (mod prime)
113+ test_power = modular_t
114+ least_i = 0
115+ for candidate_i in range (1 , remaining_s ):
116+ test_power = pow (test_power , 2 , prime )
117+ if test_power == 1 :
118+ least_i = candidate_i
119+ break
120+ else :
121+ raise ValueError (f"{ residue } is not a quadratic residue modulo { prime } " )
122+
123+ modular_b = pow (modular_c , 1 << (remaining_s - least_i - 1 ), prime )
124+ modular_r = (modular_r * modular_b ) % prime
125+ modular_c = pow (modular_b , 2 , prime )
126+ modular_t = (modular_t * modular_c ) % prime
127+ remaining_s = least_i
128+
129+ return min (modular_r , prime - modular_r )
130+
131+
132+ if __name__ == "__main__" :
133+ import doctest
134+
135+ doctest .testmod ()
0 commit comments