Finding null space of binary matrix in python - python
In factoring methods based on the quadratic sieve, finding the left null space of a binary matrix (values computed mod 2) is a crucial step. (This is also the null space of the transpose.) Does numpy or scipy have tools to do this quickly?
For reference, here is my current code:
# Row-reduce binary matrix
def binary_rr(m):
rows, cols = m.shape
l = 0
for k in range(min(rows, cols)):
print(k)
if l >= cols: break
# Swap with pivot if m[k,l] is 0
if m[k,l] == 0:
found_pivot = False
while not found_pivot:
if l >= cols: break
for i in range(k+1, rows):
if m[i,l]:
m[[i,k]] = m[[k,i]] # Swap rows
found_pivot = True
break
if not found_pivot: l += 1
if l >= cols: break # No more rows
# For rows below pivot, subtract row
for i in range(k+1, rows):
if m[i,l]: m[i] ^= m[k]
l += 1
return m
It is pretty much a straightforward implementation of Gaussian elimination, but since it's written in python it is very slow.
qwr, I found a very fast gaussian elimination routine that finishes so qiuckly that the slow point is the Quadratic Sieving or SIQS Sieving step. The gaussian elimination functions were taken from skollmans factorise.py at https://raw.githubusercontent.com/skollmann/PyFactorise/master/factorise.py
I'll soon be working on a SIQS/GNFS implementation from scratch, and hope to write something super quick for python with multithreading and possiblly cython. In the meantime, if you want something that compiles C (Alpertons ECM Engine) but uses python, you can use: https://github.com/oppressionslayer/primalitytest/ which requires you to cd into calculators directory and run make before importing p2ecm with from sfactorint import p2ecm. With that you can factorise 60 digit numbers in a few seconds.
# Requires sympy and numpy to be installed
# Adjust B and I accordingly. Set for 32 length number
# Usage:
# N=1009732533765251*1896182711927299
# factorise(N, 5000, 25000000) # Takes about 45-60 seconds on a newer computer
# N=1009732533765251*581120948477
# Linear Algebra Step finishes in 1 second, if that
# N=factorise(N, 5000, 2500000) # Takes about 5 seconds on a newer computer
# #Out[1]: 581120948477
import math
import numpy as np
from sympy import isprime
#
# siqs_ functions are the Gaussian Elimination routines right from
# skollmans factorise.py. It is the fastest Gaussian Elimination that i have
# found in python
#
def siqs_factor_from_square(n, square_indices, smooth_relations):
"""Given one of the solutions returned by siqs_solve_matrix_opt,
return the factor f determined by f = gcd(a - b, n), where
a, b are calculated from the solution such that a*a = b*b (mod n).
Return f, a factor of n (possibly a trivial one).
"""
sqrt1, sqrt2 = siqs_calc_sqrts(square_indices, smooth_relations)
assert (sqrt1 * sqrt1) % n == (sqrt2 * sqrt2) % n
return math.gcd(abs(sqrt1 - sqrt2), n)
def siqs_find_factors(n, perfect_squares, smooth_relations):
"""Perform the last step of the Self-Initialising Quadratic Field.
Given the solutions returned by siqs_solve_matrix_opt, attempt to
identify a number of (not necessarily prime) factors of n, and
return them.
"""
factors = []
rem = n
non_prime_factors = set()
prime_factors = set()
for square_indices in perfect_squares:
fact = siqs_factor_from_square(n, square_indices, smooth_relations)
if fact != 1 and fact != rem:
if isprime(fact):
if fact not in prime_factors:
print ("SIQS: Prime factor found: %d" % fact)
prime_factors.add(fact)
while rem % fact == 0:
factors.append(fact)
rem //= fact
if rem == 1:
break
if isprime(rem):
factors.append(rem)
rem = 1
break
else:
if fact not in non_prime_factors:
print ("SIQS: Non-prime factor found: %d" % fact)
non_prime_factors.add(fact)
if rem != 1 and non_prime_factors:
non_prime_factors.add(rem)
for fact in sorted(siqs_find_more_factors_gcd(non_prime_factors)):
while fact != 1 and rem % fact == 0:
print ("SIQS: Prime factor found: %d" % fact)
factors.append(fact)
rem //= fact
if rem == 1 or sfactorint_isprime(rem):
break
if rem != 1:
factors.append(rem)
return factors
def add_column_opt(M_opt, tgt, src):
"""For a matrix produced by siqs_build_matrix_opt, add the column
src to the column target (mod 2).
"""
M_opt[tgt] ^= M_opt[src]
def find_pivot_column_opt(M_opt, j):
"""For a matrix produced by siqs_build_matrix_opt, return the row of
the first non-zero entry in column j, or None if no such row exists.
"""
if M_opt[j] == 0:
return None
return lars_last_powers_of_two_trailing(M_opt[j] + 1)
def siqs_build_matrix_opt(M):
"""Convert the given matrix M of 0s and 1s into a list of numbers m
that correspond to the columns of the matrix.
The j-th number encodes the j-th column of matrix M in binary:
The i-th bit of m[i] is equal to M[i][j].
"""
m = len(M[0])
cols_binary = [""] * m
for mi in M:
for j, mij in enumerate(mi):
cols_binary[j] += "1" if mij else "0"
return [int(cols_bin[::-1], 2) for cols_bin in cols_binary], len(M), m
def siqs_solve_matrix_opt(M_opt, n, m):
"""
Perform the linear algebra step of the SIQS. Perform fast
Gaussian elimination to determine pairs of perfect squares mod n.
Use the optimisations described in [1].
[1] Koç, Çetin K., and Sarath N. Arachchige. 'A Fast Algorithm for
Gaussian Elimination over GF (2) and its Implementation on the
GAPP.' Journal of Parallel and Distributed Computing 13.1
(1991): 118-122.
"""
row_is_marked = [False] * n
pivots = [-1] * m
for j in range(m):
i = find_pivot_column_opt(M_opt, j)
if i is not None:
pivots[j] = i
row_is_marked[i] = True
for k in range(m):
if k != j and (M_opt[k] >> i) & 1: # test M[i][k] == 1
add_column_opt(M_opt, k, j)
perf_squares = []
for i in range(n):
if not row_is_marked[i]:
perfect_sq_indices = [i]
for j in range(m):
if (M_opt[j] >> i) & 1: # test M[i][j] == 1
perfect_sq_indices.append(pivots[j])
perf_squares.append(perfect_sq_indices)
return perf_squares
def sqrt_int(N):
Nsqrt = math.isqrt(N)
assert Nsqrt * Nsqrt == N
return Nsqrt
def siqs_calc_sqrts(square_indices, smooth_relations):
"""Given on of the solutions returned by siqs_solve_matrix_opt and
the corresponding smooth relations, calculate the pair [a, b], such
that a^2 = b^2 (mod n).
"""
res = [1, 1]
for idx in square_indices:
res[0] *= smooth_relations[idx][0]
res[1] *= smooth_relations[idx][1]
res[1] = sqrt_int(res[1])
return res
def quad_residue(a,n):
l=1
q=(n-1)//2
x = q**l
if x==0:
return 1
a =a%n
z=1
while x!= 0:
if x%2==0:
a=(a **2) % n
x//= 2
else:
x-=1
z=(z*a) % n
return z
def STonelli(n, p):
assert quad_residue(n, p) == 1, "not a square (mod p)"
q = p - 1
s = 0
while q % 2 == 0:
q //= 2
s += 1
if s == 1:
r = pow(n, (p + 1) // 4, p)
return r,p-r
for z in range(2, p):
#print(quad_residue(z, p))
if p - 1 == quad_residue(z, p):
break
c = pow(z, q, p)
r = pow(n, (q + 1) // 2, p)
t = pow(n, q, p)
m = s
t2 = 0
while (t - 1) % p != 0:
t2 = (t * t) % p
for i in range(1, m):
if (t2 - 1) % p == 0:
break
t2 = (t2 * t2) % p
b = pow(c, 1 << (m - i - 1), p)
r = (r * b) % p
c = (b * b) % p
t = (t * c) % p
m = i
return (r,p-r)
def build_smooth_relations(smooth_base, root_base):
smooth_relations = []
for xx in range(len(smooth_base)):
smooth_relations.append((root_base[xx], smooth_base[xx], xx))
return smooth_relations
def strailing(N):
return N>>lars_last_powers_of_two_trailing(N)
def lars_last_powers_of_two_trailing(N):
p,y=1,2
orign = N
#if orign < 17: N = N%16
N = N&15
if N == 1:
if ((orign -1) & (orign -2)) == 0: return orign.bit_length()-1
while orign&y == 0:
p+=1
y<<=1
return p
if N in [3, 7, 11, 15]: return 1
if N in [5, 13]: return 2
if N == 9: return 3
return 0
def build_matrix(factor_base, smooth_base):
factor_base = factor_base.copy()
factor_base.insert(0, 2)
sparse_matrix = []
col = 0
for xx in smooth_base:
sparse_matrix.append([])
for fx in factor_base:
count = 0
factor_found = False
while xx % fx == 0:
factor_found = True
xx=xx//fx
count+=1
if count % 2 == 0:
sparse_matrix[col].append(0)
continue
else:
if factor_found == True:
sparse_matrix[col].append(1)
else:
sparse_matrix[col].append(0)
col+=1
return np.transpose(sparse_matrix)
def get_mod_congruence(root, N, withstats=False):
r = root - N
if withstats==True:
print(f"{root} ≡ {r} mod {N}")
return r
def primes_sieve2(limit):
a = np.ones(limit, dtype=bool)
a[0] = a[1] = False
for (i, isprime) in enumerate(a):
if isprime:
yield i
for n in range(i*i, limit, i):
a[n] = False
def remove_singletons(XX):
no_singletons = []
for xx in XX:
if len(xx) != 1:
no_singletons.append(xx)
return no_singletons
def fb_sm(N, B, I):
factor_base, sieve_base, sieve_list, smooth_base, root_base = [], [], [], [], []
primes = list(primes_sieve2(B))
i,root=-1,math.isqrt(N)
for x in primes[1:]:
if quad_residue(N, x) == 1:
factor_base.append(x)
for x in range(I):
xx = get_mod_congruence((root+x)**2, N)
sieve_list.append(xx)
if xx % 2 == 0:
xx = strailing(xx+1) # using lars_last_modulus_powers_of_two(xx) bit trick
sieve_base.append(xx)
for p in factor_base:
residues = STonelli(N, p)
for r in residues:
for i in range((r-root) % p, len(sieve_list), p):
while sieve_base[i] % p == 0:
sieve_base[i] //= p
for o in range(len(sieve_list)):
# This is set to 350, which is only good for numbers
# of len < 32. Modify
# to be more dynamic for larger numbers.
if len(smooth_base) >= 350:
break
if sieve_base[o] == 1:
smooth_base.append(sieve_list[o])
root_base.append(root+o)
return factor_base, smooth_base, root_base
def isSquare(hm):
cr=math.isqrt(hm)
if cr*cr == hm:
return True
return False
def find_square(smooth_base):
for x in smooth_base:
if isSquare(x):
return (True, smooth_base.index(x))
else:
return (False, -1)
t_matrix=[]
primes=list(primes_sieve2(1000000))
def factorise(N, B=10000, I=10000000):
global primes, t_matrix
if isprime(N):
return N
for xx in primes:
if N%xx == 0:
return xx
factor_base, smooth_base, root_base = fb_sm(N,B,I)
issquare, t_matrix = find_square(smooth_base)
if issquare == True:
return math.gcd(math.isqrt(smooth_base[t_matrix])+get_mod_congruence(root_base[t_matrix], N), N)
t_matrix = build_matrix(factor_base, smooth_base)
smooth_relations = build_smooth_relations(smooth_base, root_base)
M_opt, M_n, M_m = siqs_build_matrix_opt(np.transpose(t_matrix))
perfect_squares = remove_singletons(siqs_solve_matrix_opt(M_opt, M_n, M_m))
factors = siqs_find_factors(N, perfect_squares, smooth_relations)
return factors
Related
I am trying to write a function in Python encapsulating the Miller-Rabin primality test, but it is very slow
Here is my code: import random def miller(n, k): """ takes an integer n and evaluates whether it is a prime or a composite n > 3, odd integer to be tested for primality k, rounds of testing (the more tests, the more accurate the result) """ temp = n - 1 # used to find r and d in n = 2**d * r + 1, hence temporary r = 0 while True: if temp / 2 == type(int): r += 1 else: d = temp break temp = temp / 2 for _ in range(k): a = random.SystemRandom().randrange(2, n - 2) x = a**d % n if x == 1 or x == n - 1: continue for _ in range(r - 1): x = x**2 % n if x == n - 1: continue return "Composite" return "Probably prime" Certainly there must be more effective ways to implement the algorithm in Python, so that it may handle large integers with ease? Cheers
Why classical division ("/") for large integers is much slower than integer division ("//")?
I ran into a problem: The code was very slow for 512 bit odd integers if you use classical division for (p-1)/2. But with floor division it works instantly. Is it caused by float conversion? def solovayStrassen(p, iterations): for i in range(iterations): a = random.randint(2, p - 1) if gcd(a, p) > 1: return False first = pow(a, int((p - 1) / 2), p) j = (Jacobian(a, p) + p) % p if first != j: return False return True The full code import random from math import gcd #Jacobian symbol def Jacobian(a, n): if (a == 0): return 0 ans = 1 if (a < 0): a = -a if (n % 4 == 3): ans = -ans if (a == 1): return ans while (a): if (a < 0): a = -a if (n % 4 == 3): ans = -ans while (a % 2 == 0): a = a // 2 if (n % 8 == 3 or n % 8 == 5): ans = -ans a, n = n, a if (a % 4 == 3 and n % 4 == 3): ans = -ans a = a % n if (a > n // 2): a = a - n if (n == 1): return ans return 0 def solovayStrassen(p, iterations): for i in range(iterations): a = random.randint(2, p - 1) if gcd(a, p) > 1: return False first = pow(a, int((p - 1) / 2), p) j = (Jacobian(a, p) + p) % p if first != j: return False return True def findFirstPrime(n, k): while True: if solovayStrassen(n,k): return n n+=2 a = random.getrandbits(512) if a%2==0: a+=1 print(findFirstPrime(a,100))
As noted in comments, int((p - 1) / 2) can produce garbage if p is an integer with more than 53 bits. Only the first 53 bits of p-1 are retained when converting to float for the division. >>> p = 123456789123456789123456789 >>> (p-1) // 2 61728394561728394561728394 >>> hex(_) '0x330f7ef971d8cfbe022f8a' >>> int((p-1) / 2) 61728394561728395668881408 >>> hex(_) # lots of trailing zeroes '0x330f7ef971d8d000000000' Of course the theory underlying the primality test relies on using exactly the infinitely precise value of (p-1)/2, not some approximation more-or-less good to only the first 53 most-significant bits. As also noted in a comment, using garbage is likely to make this part return earlier, not later: if first != j: return False So why is it much slower over all? Because findFirstPrime() has to call solovayStrassen() many more times to find garbage that passes by sheer blind luck. To see this, change the code to show how often the loop is trying: def findFirstPrime(n, k): count = 0 while True: count += 1 if count % 1000 == 0: print(f"at count {count:,}") if solovayStrassen(n,k): return n, count n+=2 Then add, e.g., random.seed(12) at the start of the main program so you can get reproducible results. Using floor (//) division, it runs fairly quickly, displaying (6170518232878265099306454685234429219657996228748920426206889067017854517343512513954857500421232718472897893847571955479036221948870073830638539006377457, 906) So it found a probable prime on the 906th try. But with float (/) division, I never saw it succeed by blind luck: at count 1,000 at count 2,000 at count 3,000 ... at count 1,000,000 Gave up then - "garbage in, garbage out". One other thing to note, in passing: the + p in: j = (Jacobian(a, p) + p) % p has no effect on the value of j. Right? p % p is 0.
Primenumber position search ends in too big scale what to do?
def isprime(number): counter = 0 for n in range(1,number+1): if number % n == 0: counter += 1 if counter == 2: return True This is a function to check whether the number is prime at all. my_list = [] n1 = 1 while len(my_list) <= 10000: n1 += 1 if isprime(n1) is True: my_list.append(n1) print(my_list[-1]) So this is my code for now, it works totally fine but is not optimized at all and I wanted to learn from bottom up how to make such a function faster, so that my computer is able to do the calculations. I tried to find the 10001 prime number. (started with zero so that is the reason for the <= 10000)
For numbers the size you need to solve Project Euler #7, it is sufficient to determine primality by trial division. Here is a simple primality checker using a 2,3,5-wheel, which is about twice as fast as the naive primality checker posted by Yakov Dan: def isPrime(n): # 2,3,5-wheel ws = [1,2,2,4,2,4,2,4,6,2,6] w, f = 0, 2 while f * f <= n: if n % f == 0: return False f = f + ws[w] w = w + 1 if w == 11: w = 3 return True For larger numbers, it is better to use a Miller-Rabin primality checker: def isPrime(n, k=5): # miller-rabin from random import randint if n < 2: return False for p in [2,3,5,7,11,13,17,19,23,29]: if n % p == 0: return n == p s, d = 0, n-1 while d % 2 == 0: s, d = s+1, d/2 for i in range(k): x = pow(randint(2, n-1), d, n) if x == 1 or x == n-1: continue for r in range(1, s): x = (x * x) % n if x == 1: return False if x == n-1: break else: return False return True Either of those methods will be much slower than the Sieve of Eratosthenes, invented over two thousand years ago by a Greek mathematician: def primes(n): # sieve of eratosthenes i, p, ps, m = 0, 3, [2], n // 2 sieve = [True] * m while p <= n: if sieve[i]: ps.append(p) for j in range((p*p-3)/2, m, p): sieve[j] = False i, p = i+1, p+2 return ps To solve Project Euler #7, call the sieve with n = 120000 and discard the excess primes. It will be more convenient for you to use a sieve in the form of a generator: def primegen(start=0): # stackoverflow.com/a/20660551 if start <= 2: yield 2 # prime (!) the pump if start <= 3: yield 3 # prime (!) the pump ps = primegen() # sieving primes p = next(ps) and next(ps) # first sieving prime q = p * p; D = {} # initialization def add(m, s): # insert multiple/stride while m in D: m += s # find unused multiple D[m] = s # save multiple/stride while q <= start: # initialize multiples x = (start // p) * p # first multiple of p if x < start: x += p # must be >= start if x % 2 == 0: x += p # ... and must be odd add(x, p+p) # insert in sieve p = next(ps) # next sieving prime q = p * p # ... and its square c = max(start-2, 3) # first prime candidate if c % 2 == 0: c += 1 # candidate must be odd while True: # infinite list c += 2 # next odd candidate if c in D: # c is composite s = D.pop(c) # fetch stride add(c+s, s) # add next multiple elif c < q: yield c # c is prime; yield it else: # (c == q) # add p to sieve add(c+p+p, p+p) # insert in sieve p = next(ps) # next sieving prime q = p * p # ... and its square I discuss all these things at my blog.
Fast primality tests are a huge subject. What is often fast enough is to check if a number is divisible by two or by three, and then to check all possible divisors from 4 to the square root of the number. So, try this: def is_prime(n): if n == 1: return False if n == 2: return True if n == 3: return True if n % 2 == 0 return False if n % 3 == 0 return False for d in range(5, int(n**0.5)+1,2): if n % d == 0 return False return True
Inverting matrix in python slightly off
I'm trying to use this http://www.irma-international.org/viewtitle/41011/ algorithm to invert a nxn matrix. I ran the function on this matrix [[1.0, -0.5], [-0.4444444444444444, 1.0]] and got the output [[ 1.36734694, 0.64285714] [ 0.57142857, 1.28571429]] the correct output is meant to be [[ 1.28571429, 0.64285714] [ 0.57142857, 1.28571429]] My function: def inverse(m): n = len(m) P = -1 D = 1 mI = m while True: P += 1 if m[P][P] == 0: raise Exception("Not Invertible") else: D = D * m[P][P] for j in range(n): if j != P: mI[P][j] = m[P][j] / m[P][P] for i in range(n): if i != P: mI[i][P] = -m[i][P] / m[P][P] for i in range(n): for j in range(n): if i != P and j != P: mI[i][j] = m[i][j] + (m[P][j] * mI[i][P]) mI[P][P] = 1 / m[P][P] if P == n - 1: # All elements have been looped through break return mI Where am I making my mistake?
https://repl.it/repls/PowerfulOriginalOpensoundsystem Output inverse: [[Decimal('1.285714285714285693893862813'), Decimal('0.6428571428571428469469314065')], [Decimal('0.5714285714285713877877256260'), Decimal('1.285714285714285693893862813')]] numpy: [[ 1.28571429 0.64285714] [ 0.57142857 1.28571429]] from decimal import Decimal import numpy as np def inverse(m): m = [[Decimal(n) for n in a] for a in m] n = len(m) P = -1 D = Decimal(1) mI = [[Decimal(0) for n in a] for a in m] while True: P += 1 if m[P][P] == 0: raise Exception("Not Invertible") else: D = D * m[P][P] for j in range(n): if j != P: mI[P][j] = m[P][j] / m[P][P] for i in range(n): if i != P: mI[i][P] = -m[i][P] / m[P][P] for i in range(n): for j in range(n): if i != P and j != P: mI[i][j] = m[i][j] + (m[P][j] * mI[i][P]) mI[P][P] = 1 / m[P][P] m = [[Decimal(n) for n in a] for a in mI] mI = [[Decimal(0) for n in a] for a in m] if P == n - 1: # All elements have been looped through break return m m = [[1.0, -0.5], [-0.4444444444444444, 1.0]] print(inverse(m)) print(np.linalg.inv(np.array(m))) My thought process: At first, I thought you might have lurking floating point roundoff errors. This turned out to not be true. That's what the Decimal jazz is for. Your bug is here mI = m # this just creates a pointer that points to the SAME list as m and here for i in range(n): for j in range(n): if i != P and j != P: mI[i][j] = m[i][j] + (m[P][j] * mI[i][P]) mI[P][P] = 1 / m[P][P] # you are not copying mI to m for the next iteration # you are also not zeroing mI if P == n - 1: # All elements have been looped through break return mI In adherence to the algorithm, every iteration creates a NEW a' matrix, it does not continue to modify the same old a. I inferred this to mean that in the loop invariant, a becomes a'. Works for your test case, turns out to be true.
Cube root modulo P -- how do I do this?
I am trying to calculate the cube root of a many-hundred digit number modulo P in Python, and failing miserably. I found code for the Tonelli-Shanks algorithm which supposedly is simple to modify from square roots to cube roots, but this eludes me. I've scoured the web and math libraries and a few books to no avail. Code would be wonderful, so would an algorithm explained in plain English. Here is the Python (2.6?) code for finding square roots: def modular_sqrt(a, p): """ Find a quadratic residue (mod p) of 'a'. p must be an odd prime. Solve the congruence of the form: x^2 = a (mod p) And returns x. Note that p - x is also a root. 0 is returned is no square root exists for these a and p. The Tonelli-Shanks algorithm is used (except for some simple cases in which the solution is known from an identity). This algorithm runs in polynomial time (unless the generalized Riemann hypothesis is false). """ # Simple cases # if legendre_symbol(a, p) != 1: return 0 elif a == 0: return 0 elif p == 2: return n elif p % 4 == 3: return pow(a, (p + 1) / 4, p) # Partition p-1 to s * 2^e for an odd s (i.e. # reduce all the powers of 2 from p-1) # s = p - 1 e = 0 while s % 2 == 0: s /= 2 e += 1 # Find some 'n' with a legendre symbol n|p = -1. # Shouldn't take long. # n = 2 while legendre_symbol(n, p) != -1: n += 1 # Here be dragons! # Read the paper "Square roots from 1; 24, 51, # 10 to Dan Shanks" by Ezra Brown for more # information # # x is a guess of the square root that gets better # with each iteration. # b is the "fudge factor" - by how much we're off # with the guess. The invariant x^2 = ab (mod p) # is maintained throughout the loop. # g is used for successive powers of n to update # both a and b # r is the exponent - decreases with each update # x = pow(a, (s + 1) / 2, p) b = pow(a, s, p) g = pow(n, s, p) r = e while True: t = b m = 0 for m in xrange(r): if t == 1: break t = pow(t, 2, p) if m == 0: return x gs = pow(g, 2 ** (r - m - 1), p) g = (gs * gs) % p x = (x * gs) % p b = (b * g) % p r = m def legendre_symbol(a, p): """ Compute the Legendre symbol a|p using Euler's criterion. p is a prime, a is relatively prime to p (if p divides a, then a|p = 0) Returns 1 if a has a square root modulo p, -1 otherwise. """ ls = pow(a, (p - 1) / 2, p) return -1 if ls == p - 1 else ls Source: Computing modular square roots in Python
Note added later: In the Tonelli-Shanks algorithm and here it is assumed that p is prime. If we could compute modular square roots to composite moduli quickly in general we could factor numbers quickly. I apologize for assuming that you knew that p was prime. See here or here. Note that the numbers modulo p are the finite field with p elements. Edit: See this also (this is the grandfather of those papers.) The easy part is when p = 2 mod 3, then everything is a cube and athe cube root of a is just a**((2*p-1)/3) %p Added: Here is code to do all but the primes 1 mod 9. I'll try to get to it this weekend. If no one else gets to it first #assumes p prime returns cube root of a mod p def cuberoot(a, p): if p == 2: return a if p == 3: return a if (p%3) == 2: return pow(a,(2*p - 1)/3, p) if (p%9) == 4: root = pow(a,(2*p + 1)/9, p) if pow(root,3,p) == a%p: return root else: return None if (p%9) == 7: root = pow(a,(p + 2)/9, p) if pow(root,3,p) == a%p: return root else: return None else: print "Not implemented yet. See the second paper"
Here is a complete code in pure python. By considering special cases first, it is almost as fast as the Peralta algoritm. #assumes p prime, it returns all cube roots of a mod p def cuberoots(a, p): #Non-trivial solutions of x**r=1 def onemod(p,r): sols=set() t=p-2 while len(sols)<r: g=pow(t,(p-1)//r,p) while g==1: t-=1; g=pow(t,(p-1)//r,p) sols.update({g%p,pow(g,2,p),pow(g,3,p)}) t-=1 return sols def solutions(p,r,root,a): todo=onemod(p,r) return sorted({(h*root)%p for h in todo if pow(h*root,3,p)==a}) #---MAIN--- a=a%p if p in [2,3] or a==0: return [a] if p%3 == 2: return [pow(a,(2*p - 1)//3, p)] #One solution #There are three or no solutions #No solution if pow(a,(p-1)//3,p)>1: return [] if p%9 == 7: #[7, 43, 61, 79, 97, 151] root = pow(a,(p + 2)//9, p) if pow(root,3,p) == a: return solutions(p,3,root,a) else: return [] if p%9 == 4: #[13, 31, 67, 103, 139] root = pow(a,(2*p + 1)//9, p) print(root) if pow(root,3,p) == a: return solutions(p,3,root,a) else: return [] if p%27 == 19: #[19, 73, 127, 181] root = pow(a,(p + 8)//27, p) return solutions(p,9,root,a) if p%27 == 10: #[37, 199, 307] root = pow(a,(2*p +7)//27, p) return solutions(p,9,root,a) #We need a solution for the remaining cases return tonelli3(a,p,True) An extension of Tonelli-Shank algorithm. def tonelli3(a,p,many=False): def solution(p,root): g=p-2 while pow(g,(p-1)//3,p)==1: g-=1 #Non-trivial solution of x**3=1 g=pow(g,(p-1)//3,p) return sorted([root%p,(root*g)%p,(root*g**2)%p]) #---MAIN--- a=a%p if p in [2,3] or a==0: return [a] if p%3 == 2: return [pow(a,(2*p - 1)//3, p)] #One solution #No solution if pow(a,(p-1)//3,p)>1: return [] #p-1=3**s*t s=0 t=p-1 while t%3==0: s+=1; t//=3 #Cubic nonresidu b b=p-2 while pow(b,(p-1)//3,p)==1: b-=1 c,r=pow(b,t,p),pow(a,t,p) c1,h=pow(c,3**(s-1),p),1 c=pow(c,p-2,p) #c=inverse modulo p for i in range(1,s): d=pow(r,3**(s-i-1),p) if d==c1: h,r=h*c,r*pow(c,3,p) elif d!=1: h,r=h*pow(c,2,p),r*pow(c,6,p) c=pow(c,3,p) if (t-1)%3==0: k=(t-1)//3 else: k=(t+1)//3 r=pow(a,k,p)*h if (t-1)%3==0: r=pow(r,p-2,p) #r=inverse modulo p if pow(r,3,p)==a: if many: return solution(p,r) else: return [r] else: return [] You can test it using: test=[(17,1459),(17,1000003),(17,10000019),(17,1839598566765178548164758165715596714561757494507845814465617175875455789047)] for a,p in test: print "y^3=%s modulo %s"%(a,p) sol=cuberoots(a,p) print "p%s3=%s"%("%",p%3),sol,"--->",map(lambda t: t^3%p,sol) which should yield (fast): y^3=17 modulo 1459 p%3=1 [483, 329, 647] ---> [17, 17, 17] y^3=17 modulo 1000003 p%3=1 [785686, 765339, 448981] ---> [17, 17, 17] y^3=17 modulo 10000019 p%3=2 [5188997] ---> [17] y^3=17 modulo 1839598566765178548164758165715596714561757494507845814465617175875455789047 p%3=1 [753801617033579226225229608063663938352746555486783903392457865386777137044, 655108821219252496141403783945148550782812009720868259303598196387356108990, 430688128512346825798124773706784225426198929300193651769561114101322543013] ---> [17, 17, 17]
I converted the code by Rolandb above into python3. If you put this into a file, you can import it and run it in python3, and if you run it standalone it will validate that it works. #! /usr/bin/python3 def ts_cubic_modular_roots (a, p): """ python3 version of cubic modular root code posted by Rolandb on stackoverflow. With new formatting. https://stackoverflow.com/questions/6752374/cube-root-modulo-p-how-do-i-do-this """ #Non-trivial solution of x**r = 1 def onemod (p, r): t = p - 2 while pow (t, (p - 1) // r, p) == 1: t -= 1 return pow (t, (p - 1) // r, p) def solution(p, root): g = onemod (p, 3) return [root % p, (root * g) % p, (root * (g ** 2)) % p] #---MAIN--- a = a % p if p in [2, 3] or a == 0: return [a] if p % 3 == 2: return [pow (a, ((2 * p) - 1) // 3, p)] #Eén oplossing #There are 3 or no solutions #No solution if pow (a, (p-1) // 3, p) > 1: return [] if p % 9 == 4: #[13, 31, 67] root = pow (a, ((2 * p) + 1) // 9, p) if pow (root, 3, p) == a: return solution (p, root) else: return [] if p % 9 == 7: #[7, 43, 61, 79, 97 root = pow (a, (p + 2) // 9, p) if pow (root, 3, p) == a: return solution (p, root) else: return [] if p % 27 == 10: #[37, 199] root = pow (a, ((2 * p) + 7) // 27, p) h = onemod (p, 9) for i in range (0,9): if pow (root, 3, p) == a: return solution (p, root) root *= h return [] if p % 27 == 19: #[19, 73, 127, 181] root = pow (a, (p + 8)//27, p) h = onemod (p, 9) for i in range (0, 9): if pow (root, 3, p) == a: return solution (p, root) root *= h return [] #We need an algorithm for the remaining cases return tonelli3 (a, p, True) def tonelli3 (a, p, many = False): #Non-trivial solution of x**r = 1 def onemod (p, r): t = p - 2 while pow (t, (p - 1) // r, p) == 1: t -= 1 return pow (t, (p - 1) // r, p) def solution (p, root): g = onemod (p, 3) return [root % p, (root * g) % p, (root * (g**2)) % p] #---MAIN--- a = a % p if p in [2, 3] or a == 0: return [a] if p % 3 == 2: return [pow (a, ((2 * p) - 1) // 3, p)] #Eén oplossing #No solution if pow (a, (p - 1) // 3, p) > 1: return [] #p-1 = 3^s*t s = 0 t = p - 1 while t % 3 == 0: s += 1 t //= 3 #Cubic nonresidu b b = p - 2 while pow (b, (p - 1) // 3, p) == 1: b -= 1 c, r = pow (b, t, p), pow (a, t, p) c1, h = pow (c, 3 ^ (s - 1), p), 1 c = pow (c, p - 2, p) #c=inverse_mod(Integer(c), p) for i in range (1, s): d = pow (r, 3 ^ (s - i - 1), p) if d == c1: h, r = h * c, r * pow (c, 3, p) elif d != 1: h, r = h * pow (c, 2, p), r * pow (c, 6, p) c = pow (c, 3, p) if (t - 1) % 3 == 0: k = (t - 1) // 3 else: k = (t + 1) // 3 r = pow (a, k, p) * h if (t - 1) % 3 == 0: r = pow (r, p - 2, p) #r=inverse_mod(Integer(r), p) if pow (r, 3, p) == a: if many: return solution(p, r) else: return [r] else: return [] if '__name__' == '__main__': import ts_cubic_modular_roots tscr = ts_cubic_modular_roots.ts_cubic_modular_roots test=[(17,1459),(17,1000003),(17,10000019),(17,1839598566765178548164758165715596714561757494507845814465617175875455789047)] for a,p in test: print ("y**3=%s modulo %s"%(a,p)) sol = tscr (a,p) print ("p%s3=%s"%("%",p % 3), sol, [pow (t,3,p) for t in sol]) # results of the above #y**3=17 modulo 1459 #p%3=1 [] [] #y**3=17 modulo 1000003 #p%3=1 [785686, 765339, 448981] [17, 17, 17] #y**3=17 modulo 10000019 #p%3=2 [5188997] [17] #y**3=17 modulo 1839598566765178548164758165715596714561757494507845814465617175875455789047 #p%3=1 [753801617033579226225229608063663938352746555486783903392457865386777137044, 655108821219252496141403783945148550782812009720868259303598196387356108990, 430688128512346825798124773706784225426198929300193651769561114101322543013] [17, 17, 17]
Sympy has a nice implementation for arbitrary integer modulo and arbitrary power: https://docs.sympy.org/latest/modules/ntheory.html#sympy.ntheory.residue_ntheory.nthroot_mod from sympy.ntheory.residue_ntheory import nthroot_mod a = 17 n = 3 modulo = 10000019 roots = nthroot_mod(a, n, modulo) print(roots) # 5188997