├── __pycache__ └── MillerRabin.cpython-36.pyc ├── README.md ├── MillerRabin.py ├── Pollard.py ├── Quadratic Sieve.py └── Quadratic Sieve-Beta.py /__pycache__/MillerRabin.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maosef/Quadratic-Sieve/HEAD/__pycache__/MillerRabin.cpython-36.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quadratic-Sieve 2 | Python code for the single-polynomial version of Quadratic Sieve, no bells or whistles. Factors an integer N, using a chosen factor base of P primes, and a sieve interval. 3 | 4 | Todo: 5 | Implement Numpy 6 | 7 | Add data analysis tools 8 | 9 | Implement Cython 10 | 11 | Implement Multiple Polynomials 12 | 13 | Implement General Number Field Sieve (eventually...) 14 | 15 | This is a self-pursued project. Over time I will further clean up and optimize the algorithm, and I'll probably implement the multiple polynomial version soon. 16 | 17 | Integer Factorization is one of those lovely topics which is simple in concept, but notoriously difficult to master. Yet we rely on its security almost constantly, through cryptosystems such as RSA. 18 | This project was an exploration of that realm, one which took me through a web of number theory, algorithms, cryptography, and linear algebra. 19 | Even the "basic" version of Quadratic Sieve took an embarrasing amount of time to finish, but the experience was well worth it. 20 | I hope this could help other aspiring students with understanding a little bit about integer factorization. Maybe for identity theft. 21 | 22 | Simplified Gaussian Elimination was taken from this source: https://www.cs.umd.edu/~gasarch/TOPICS/factoring/fastgauss.pdf 23 | Tonelli-Shanks for modular square root: https://gist.github.com/LaurentMazare/6745649 24 | -------------------------------------------------------------------------------- /MillerRabin.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | _mrpt_num_trials = 5 # number of bases to test 4 | 5 | def is_probable_prime(n): 6 | """ 7 | Miller-Rabin primality test. 8 | 9 | A return value of False means n is certainly not prime. A return value of 10 | True means n is very likely a prime. 11 | 12 | >>> is_probable_prime(1) 13 | Traceback (most recent call last): 14 | ... 15 | AssertionError 16 | >>> is_probable_prime(2) 17 | True 18 | >>> is_probable_prime(3) 19 | True 20 | >>> is_probable_prime(4) 21 | False 22 | >>> is_probable_prime(5) 23 | True 24 | >>> is_probable_prime(123456789) 25 | False 26 | 27 | >>> primes_under_1000 = [i for i in range(2, 1000) if is_probable_prime(i)] 28 | >>> len(primes_under_1000) 29 | 168 30 | >>> primes_under_1000[-10:] 31 | [937, 941, 947, 953, 967, 971, 977, 983, 991, 997] 32 | 33 | >>> is_probable_prime(6438080068035544392301298549614926991513861075340134\ 34 | 3291807343952413826484237063006136971539473913409092293733259038472039\ 35 | 7133335969549256322620979036686633213903952966175107096769180017646161\ 36 | 851573147596390153) 37 | True 38 | 39 | >>> is_probable_prime(7438080068035544392301298549614926991513861075340134\ 40 | 3291807343952413826484237063006136971539473913409092293733259038472039\ 41 | 7133335969549256322620979036686633213903952966175107096769180017646161\ 42 | 851573147596390153) 43 | False 44 | """ 45 | assert n >= 2 46 | # special case 2 47 | if n == 2: 48 | return True 49 | # ensure n is odd 50 | if n % 2 == 0: 51 | return False 52 | # write n-1 as 2**s * d 53 | # repeatedly try to divide n-1 by 2 54 | s = 0 55 | d = n-1 56 | while True: 57 | quotient, remainder = divmod(d, 2) 58 | if remainder == 1: 59 | break 60 | s += 1 61 | d = quotient 62 | assert(2**s * d == n-1) 63 | 64 | # test the base a to see whether it is a witness for the compositeness of n 65 | def try_composite(a): 66 | if pow(a, d, n) == 1: 67 | return False 68 | for i in range(s): 69 | if pow(a, 2**i * d, n) == n-1: 70 | return False 71 | return True # n is definitely composite 72 | 73 | for i in range(_mrpt_num_trials): 74 | a = random.randrange(2, n) 75 | if try_composite(a): 76 | return False 77 | 78 | return True # no base tested showed n as composite 79 | 80 | 81 | 82 | 83 | 84 | def _try_composite(a, d, n, s): 85 | if pow(a, d, n) == 1: 86 | return False 87 | for i in range(s): 88 | if pow(a, 2**i * d, n) == n-1: 89 | return False 90 | return True # n is definitely composite 91 | 92 | def is_prime(n, _precision_for_huge_n=16): 93 | if n in _known_primes or n in (0, 1): 94 | return True 95 | if any((n % p) == 0 for p in _known_primes): 96 | return False 97 | d, s = n - 1, 0 98 | while not d % 2: 99 | d, s = d >> 1, s + 1 100 | # Returns exact according to http://primes.utm.edu/prove/prove2_3.html 101 | if n < 1373653: 102 | return not any(_try_composite(a, d, n, s) for a in (2, 3)) 103 | if n < 25326001: 104 | return not any(_try_composite(a, d, n, s) for a in (2, 3, 5)) 105 | if n < 118670087467: 106 | if n == 3215031751: 107 | return False 108 | return not any(_try_composite(a, d, n, s) for a in (2, 3, 5, 7)) 109 | if n < 2152302898747: 110 | return not any(_try_composite(a, d, n, s) for a in (2, 3, 5, 7, 11)) 111 | if n < 3474749660383: 112 | return not any(_try_composite(a, d, n, s) for a in (2, 3, 5, 7, 11, 13)) 113 | if n < 341550071728321: 114 | return not any(_try_composite(a, d, n, s) for a in (2, 3, 5, 7, 11, 13, 17)) 115 | # otherwise 116 | return not any(_try_composite(a, d, n, s) 117 | for a in _known_primes[:_precision_for_huge_n]) 118 | 119 | _known_primes = [2, 3] 120 | _known_primes += [x for x in range(5, 1000, 2) if is_prime(x)] 121 | 122 | -------------------------------------------------------------------------------- /Pollard.py: -------------------------------------------------------------------------------- 1 | import random 2 | import math 3 | import timeit 4 | 5 | def gcd(a,b): #greatest common divisor 6 | if b == 0: 7 | return a 8 | elif a >= b: 9 | return gcd(b,a % b) 10 | else: 11 | return gcd(b,a) 12 | 13 | def trial(n): #naive factoring algorithm 14 | if n <= 3: 15 | return n 16 | else: 17 | iters = 0 18 | i = 3 19 | while i*i <= n: 20 | if n % i == 0: 21 | break 22 | iters += 1 23 | i += 2 24 | 25 | return iters 26 | 27 | def rho_floyd(n): #My pollard's rho using Floyd's cycle detection 28 | factors = [] 29 | iters = 0 30 | x,c = random.randint(1,n-1),random.randint(1,n-1) 31 | y = x 32 | divisor = 1 33 | 34 | while divisor == 1: 35 | x = ((x*x)+c)%n 36 | y = ((y*y)+c)%n 37 | y = ((y*y)+c)%n 38 | divisor = gcd(abs(x - y), n) 39 | iters += 3 #number of times pseudo is executed 40 | 41 | if divisor == n: 42 | return "failure" 43 | 44 | else: 45 | factors.append(divisor) 46 | factors.append(int(n/divisor)) 47 | return factors,iters,stop - start 48 | 49 | def rho_brent(n):#My pollard's rho using Brent's cycle detection algorithm 50 | x, c = random.randint(1,n-1), random.randint(1,n-1) 51 | y = x 52 | power = lam = d = 1 #lambda is steps taken, power is step limit, d is divisor 53 | factors = [] 54 | iters = 0 55 | 56 | while d == 1: 57 | if power == lam: #time to start new power of 2? 58 | x = y 59 | power *= 2 60 | lam = 0 #reset 61 | 62 | y = ((y*y)+c)%n #advance hare 63 | lam += 1 #increment step count 64 | d = gcd(abs(x - y), n) 65 | iters += 1 66 | 67 | if d == n: 68 | return "failure" 69 | 70 | return iters 71 | 72 | 73 | def brent(N): #The actual Brent's method 74 | iters = 0 75 | if N % 2 == 0: #test if even 76 | return 2 77 | y, c, m = random.randint(1, N - 1), 1, random.randint(1, N - 1) 78 | #y is hare 79 | g, r, q = 1, 1, 1 #g is divisor, r is power 80 | while g == 1: 81 | x = y #x is tortoise, teleport to hare 82 | for i in range(r): #executes pseudo(y) once, then by powers of two 83 | y = ((y * y) % N + c) % N 84 | iters += 1 85 | #print("y is now " + str(y)) 86 | k = 0 # weird step counter 87 | while k < r and g == 1: #its not time to start a new power of two 88 | ys = y #store y 89 | #print(m,r-k,min(m, r - k)) 90 | for i in range(min(m, r - k)): 91 | y = ((y * y) % N + c) % N #pseudo(hare) 92 | q = q * (abs(x - y)) % N 93 | iters +=1 94 | g = gcd(q, N) 95 | #print("g is "+str(g)) 96 | k = k + m #k now greater than r, so go outside the loop and reset k 97 | r *= 2 98 | #print(m,k,r) 99 | if g == N: #reached end, fail 100 | #print("g=N") 101 | while True: 102 | ys = ((ys * ys) % N + c) % N 103 | g = gcd(abs(x - ys), N) 104 | iters += 1 105 | if g > 1: 106 | break 107 | return g 108 | 109 | def floyd(N): 110 | iters = 0 111 | if N%2==0: 112 | return 2 113 | x = random.randint(1, N-1) 114 | y = x 115 | c = 2#random.randint(1, N-1) 116 | g = 1 117 | while g==1: 118 | x = ((x*x)%N+c)%N 119 | y = ((y*y)%N+c)%N 120 | y = ((y*y)%N+c)%N 121 | iters += 3 122 | g = gcd(abs(x-y),N) 123 | 124 | return iters 125 | 126 | def average(function, n):#finds the average time and number of iterations 127 | output = [] 128 | trials = 0 129 | start = timeit.default_timer() 130 | for i in range(50): #repeat algorithm 131 | trials += function(n) 132 | stop = timeit.default_timer() 133 | 134 | output.append((stop-start)/50) 135 | output.append(trials/50) 136 | return output 137 | 138 | def test(function):#reads and creates semiprimes, then tests and stores average performance 139 | 140 | f = open("primes1.txt") 141 | z = 0 142 | semiprimes = [] 143 | for line in f: 144 | if(z < 200 and z % 2 == 0 and z != 0):#select every 10000th line 145 | ls = line.split(" ") 146 | semiprimes.append(2*int(ls[1])*int(ls[2]))#make semiprimes 147 | semiprimes.append(3*int(ls[3])*int(ls[4]))#make semiprimes 148 | semiprimes.append(5*int(ls[5])*int(ls[6]))#make semiprimes 149 | semiprimes.append(7*int(ls[7])*int(ls[8]))#make semiprimes 150 | z+=1 151 | #print(semiprimes) 152 | print(str(len(semiprimes)) + " semiprimes found.") 153 | 154 | fileName = "trial.txt" 155 | 156 | o = open(fileName,"w") 157 | o.close() 158 | o = open(fileName,"a+") 159 | 160 | for n in semiprimes: 161 | output = average(function, n) 162 | time = output[0] 163 | iters = output[1] 164 | print(iters) 165 | 166 | o.write(str(n) + " " + str(time) + " " + str(iters)) 167 | o.write("\n") 168 | o.close() 169 | print("Finished.") 170 | 171 | '''s = input("Enter a number to factor: ") 172 | #while(s.lower()!="exit"): 173 | print(brent(int(s))) 174 | #print(find_iters(eval(s))) 175 | s = input("Enter a number to factor (exit to exit): ")''' 176 | -------------------------------------------------------------------------------- /Quadratic Sieve.py: -------------------------------------------------------------------------------- 1 | from math import fabs, ceil, sqrt, exp, log 2 | import random 3 | from MillerRabin import is_probable_prime 4 | from itertools import chain 5 | 6 | def gcd(a,b): # Euclid's algorithm 7 | if b == 0: 8 | return a 9 | elif a >= b: 10 | return gcd(b,a % b) 11 | else: 12 | return gcd(b,a) 13 | 14 | def isqrt(n): # Newton's method, returns exact int for large squares 15 | x = n 16 | y = (x + 1) // 2 17 | while y < x: 18 | x = y 19 | y = (x + n // x) // 2 20 | return x 21 | 22 | def mprint(M): #prints a matrix in readable form 23 | for row in M: 24 | print(row) 25 | 26 | def prime_gen(n): # sieve of Eratosthenes, generates primes up to a bound n 27 | if n < 2: 28 | return [] 29 | 30 | nums = [] 31 | isPrime = [] 32 | 33 | for i in range(0, n+1):#Creates list of numbers from 0 to n 34 | nums.append(i) 35 | isPrime.append(True) 36 | 37 | isPrime[0]=False 38 | isPrime[1]=False 39 | 40 | for j in range(2,int(n/2)):#tries all size gaps that make sense 41 | if isPrime[j] == True: 42 | for i in range(2*j,n+1,j):#starts from j+j, jumps by gap size j and crosses out that number 43 | isPrime[i] = False 44 | 45 | primes = [] 46 | for i in range(0, n+1):#Adds leftovers 47 | if isPrime[i] == True: 48 | primes.append(nums[i]) 49 | 50 | return primes 51 | 52 | 53 | def pollard(N,factors): # completely factors N using Pollard Rho, given a list 54 | 55 | rem = N 56 | while True: 57 | if is_probable_prime(rem): 58 | factors.append(rem) 59 | break 60 | 61 | f = brent(rem) 62 | while f == rem:#ensures pollard rho returns a smaller factor 63 | f = brent(rem) 64 | 65 | if f and f < rem: #found a factor 66 | if is_probable_prime(f): #ensure f is prime 67 | #print("Pollard rho (Brent): Prime factor found: %s" % f) 68 | factors.append(f) 69 | rem = rem//f #other factor 70 | else: #factor is composite 71 | #print("Pollard rho (Brent): Non-prime factor found: %s" % f) 72 | rem_f = factor(f,factors) #recursive part 73 | rem = (rem//f) * rem_f #combines the two remainders 74 | factors.remove(rem_f)#removes tricky duplicate that got appended in 1st if stmt 75 | else: #no more factors found, rem is prime 76 | #print("No (more) small factors found.") 77 | break 78 | 79 | return rem 80 | 81 | 82 | def legendre(a, p): #legendre symbol of (a/p) 83 | return pow(a, (p - 1) // 2, p) 84 | 85 | def tonelli(n, p): #tonelli-shanks to solve modular square root, x^2 = N (mod p) 86 | assert legendre(n, p) == 1, "not a square (mod p)" 87 | q = p - 1 88 | s = 0 89 | while q % 2 == 0: 90 | q //= 2 91 | s += 1 92 | if s == 1: 93 | r = pow(n, (p + 1) // 4, p) 94 | return r,p-r 95 | for z in range(2, p): 96 | if p - 1 == legendre(z, p): 97 | break 98 | c = pow(z, q, p) 99 | r = pow(n, (q + 1) // 2, p) 100 | t = pow(n, q, p) 101 | m = s 102 | t2 = 0 103 | while (t - 1) % p != 0: 104 | t2 = (t * t) % p 105 | for i in range(1, m): 106 | if (t2 - 1) % p == 0: 107 | break 108 | t2 = (t2 * t2) % p 109 | b = pow(c, 1 << (m - i - 1), p) 110 | r = (r * b) % p 111 | c = (b * b) % p 112 | t = (t * c) % p 113 | m = i 114 | 115 | return (r,p-r) 116 | 117 | 118 | def size_bound(N): # finds optimal factor base size and interval 119 | 120 | F = pow(exp(sqrt(log(N)*log(log(N)))),sqrt(2)/4) 121 | I = F**3 122 | print(F,I) 123 | return int(F),int(I) 124 | 125 | 126 | def find_base(N,B): 127 | # generates a B-smooth factor base 128 | 129 | factor_base = [] 130 | primes = prime_gen(B) 131 | #print(primes) 132 | 133 | for p in primes: # such that N is a quadratic residue mod p 134 | if legendre(N,p) == 1: 135 | factor_base.append(p) 136 | return factor_base 137 | 138 | def find_base1(N,F): 139 | # generates an F-long factor base 140 | 141 | factor_base = [] 142 | primes = prime_gen(P*10) # kind of arbitrary 143 | #print(primes) 144 | 145 | for p in primes: 146 | if len(factor_base) == F: 147 | break 148 | if legendre(N,p) == 1: 149 | factor_base.append(p) 150 | return factor_base 151 | 152 | def find_smooth(factor_base,N,I): 153 | # tries to find B-smooth numbers in sieve_seq, using sieving 154 | 155 | def sieve_prep(N,sieve_int): 156 | # generates a sequence from Y(x) = x^2 - N, starting at x = root 157 | sieve_seq = [x**2 - N for x in range(root,root+sieve_int)] 158 | #sieve_seq_neg = [x**2 - N for x in range(root,root-sieve_int,-1)] 159 | return sieve_seq 160 | 161 | sieve_seq = sieve_prep(N,I) 162 | sieve_list = sieve_seq.copy() # keep a copy of sieve_seq for later 163 | 164 | if factor_base[0] == 2: 165 | i = 0 166 | while sieve_list[i] % 2 != 0: 167 | i += 1 168 | for j in range(i,len(sieve_list),2): # found the 1st even term, now every other term will also be even 169 | while sieve_list[j] % 2 == 0: #account for powers of 2 170 | sieve_list[j] //= 2 171 | 172 | for p in factor_base[1:]: #not including 2 173 | residues = tonelli(N,p) #finds x such that x^2 = n (mod p). There are two start solutions 174 | #print(residues) 175 | 176 | for r in residues: 177 | print((r-root)%p) 178 | for i in range((r-root) % p, len(sieve_list), p): # Now every pth term will also be divisible 179 | while sieve_list[i] % p == 0: #account for prime powers 180 | sieve_list[i] //= p 181 | 182 | xlist = [] #original x terms 183 | smooth_nums = [] 184 | indices = [] # index of discovery 185 | 186 | for i in range(len(sieve_list)): 187 | if len(smooth_nums) >= len(factor_base)+T: #probability of no solutions is 2^-T 188 | break 189 | if sieve_list[i] == 1: # found B-smooth number 190 | smooth_nums.append(sieve_seq[i]) 191 | xlist.append(i+root) 192 | indices.append(i) 193 | return(smooth_nums,xlist,indices) 194 | 195 | def find_smooth1(factor_base,N,I): #accounts for -1 in factor base 196 | 197 | def sieve_prep(N,I): 198 | # generates a sequence from Y(x) = x^2 - N, starting at x = root - I 199 | 200 | xlist = [x for x in range(root-I,root+I)] 201 | sieve_seq = [x**2 - N for x in range(root-I,root+I)] 202 | 203 | return sieve_seq 204 | 205 | sieve_seq = sieve_prep(N,I) 206 | sieve_list = sieve_seq.copy() # keep a copy of sieve_seq for later 207 | 208 | if factor_base[0] == 2: 209 | i = 0 210 | while sieve_list[i] % 2 != 0: 211 | i += 1 212 | for j in range(i,len(sieve_list),2): # found the 1st even term, now every other term will also be even 213 | while sieve_list[j] % 2 == 0: #account for powers of 2 214 | sieve_list[j] //= 2 215 | 216 | for p in factor_base[1:]: #not including 2 217 | residues = tonelli(N,p) #finds x such that x^2 = n (mod p). There are two start solutions 218 | #print(residues) 219 | 220 | for r in residues: 221 | 222 | for i in range((r-root+I) % p, len(sieve_list), p): # Now every pth term will also be divisible 223 | while sieve_list[i] % p == 0: #account for prime powers 224 | sieve_list[i] //= p 225 | 226 | for i in range(((r-root+I) % p)+I, 0, -p): # Going the negative direction! 227 | while sieve_list[i] % p == 0: #account for prime powers 228 | sieve_list[i] //= p 229 | 230 | #print(sieve_list) 231 | indices = [] # index of discovery 232 | xlist = [] #original x terms 233 | smooth_nums = [] 234 | 235 | for i in range(len(sieve_list)): 236 | if len(smooth_nums) >= len(factor_base)+T: #probability of no solutions is 2^-T 237 | break 238 | elif sieve_list[i] == 1 or sieve_list[i] == -1: # found B-smooth number 239 | smooth_nums.append(sieve_seq[i]) 240 | xlist.append(i+root-I) 241 | indices.append(i) 242 | return(smooth_nums,xlist,indices) 243 | 244 | def build_matrix(smooth_nums,factor_base): 245 | # generates exponent vectors mod 2 from previously obtained smooth numbers, then builds matrix 246 | 247 | def factor(n,factor_base):#trial division from factor base 248 | factors = [] 249 | if n < 0: 250 | factors.append(-1) 251 | for p in factor_base: 252 | if p == -1: 253 | pass 254 | else: 255 | while n % p == 0: 256 | factors.append(p) 257 | n //= p 258 | return factors 259 | 260 | M = [] 261 | factor_base.insert(0,-1) 262 | 263 | for n in smooth_nums: 264 | exp_vector = [0]*(len(factor_base)) 265 | n_factors = factor(n,factor_base) 266 | print(n,n_factors) 267 | for i in range(len(factor_base)): 268 | if factor_base[i] in n_factors: 269 | exp_vector[i] = (exp_vector[i] + n_factors.count(factor_base[i])) % 2 270 | 271 | #print(n_factors, exp_vector) 272 | if 1 not in exp_vector: #search for squares 273 | return True, n 274 | else: 275 | pass 276 | 277 | M.append(exp_vector) 278 | 279 | #print("Matrix built:") 280 | #mprint(M) 281 | return(False, transpose(M)) 282 | 283 | 284 | def transpose(matrix): 285 | #transpose matrix so columns become rows, makes list comp easier to work with 286 | new_matrix = [] 287 | for i in range(len(matrix[0])): 288 | new_row = [] 289 | for row in matrix: 290 | new_row.append(row[i]) 291 | new_matrix.append(new_row) 292 | return(new_matrix) 293 | 294 | '''def optimize(M): 295 | for row in M: #matrix optimization; delete factors that only occur once 296 | if row.count(1) == 1: 297 | for r in M: 298 | del r[row.index(1)] 299 | del row 300 | 301 | return(M)''' 302 | 303 | def gauss_elim(M): 304 | #reduced form of gaussian elimination, finds rref and reads off the nullspace 305 | #https://www.cs.umd.edu/~gasarch/TOPICS/factoring/fastgauss.pdf 306 | #mprint(M) 307 | #M = optimize(M) 308 | marks = [False]*len(M[0]) 309 | 310 | for i in range(len(M)): #do for all rows 311 | row = M[i] 312 | #print(row) 313 | 314 | for num in row: #search for pivot 315 | if num == 1: 316 | #print("found pivot at column " + str(row.index(num)+1)) 317 | j = row.index(num) # column index 318 | marks[j] = True 319 | 320 | for k in chain(range(0,i),range(i+1,len(M))): #search for other 1s in the same column 321 | if M[k][j] == 1: 322 | for i in range(len(M[k])): 323 | M[k][i] = (M[k][i] + row[i])%2 324 | break 325 | 326 | print(marks) 327 | M = transpose(M) 328 | #mprint(M) 329 | 330 | sol_rows = [] 331 | for i in range(len(marks)): #find free columns (which have now become rows) 332 | if marks[i]== False: 333 | free_row = [M[i],i] 334 | sol_rows.append(free_row) 335 | 336 | if not sol_rows: 337 | return("No solution found. Need more smooth numbers.") 338 | 339 | print("Found {} potential solutions".format(len(sol_rows))) 340 | return sol_rows,marks,M 341 | 342 | def solve_row(sol_rows,M,marks,K=0): 343 | solution_vec, indices = [],[] 344 | free_row = sol_rows[K][0] # may be multiple K 345 | for i in range(len(free_row)): 346 | if free_row[i] == 1: 347 | indices.append(i) 348 | 349 | for r in range(len(M)): #rows with 1 in the same column will be dependent 350 | for i in indices: 351 | if M[r][i] == 1 and marks[r]: 352 | solution_vec.append(r) 353 | break 354 | 355 | print("Found linear dependencies at rows "+ str(solution_vec)) 356 | solution_vec.append(sol_rows[K][1]) 357 | return(solution_vec) 358 | 359 | def solve(solution_vec,smooth_nums,xlist,N): 360 | 361 | solution_nums = [smooth_nums[i] for i in solution_vec] 362 | x_nums = [xlist[i] for i in solution_vec] 363 | print(solution_nums,x_nums) 364 | 365 | Asquare = 1 366 | for n in solution_nums: 367 | Asquare *= n 368 | #print(Asquare) 369 | 370 | b = 1 371 | for n in x_nums: 372 | b *= n 373 | 374 | a = isqrt(Asquare) 375 | print(str(a)+"^2 = "+str(b)+"^2 mod "+str(N)) 376 | 377 | factor = gcd(b-a,N) 378 | return factor 379 | 380 | 381 | def QS(n,B,I): 382 | #single polynomial version of quadratic sieve, given smoothness bound B and sieve interval I 383 | 384 | global N 385 | global root 386 | global T #tolerance factor 387 | N,root,K,T = n,int(sqrt(n)),0,1 388 | 389 | if is_probable_prime(N): 390 | return "prime" 391 | 392 | if isinstance(sqrt(N),int): 393 | return isqrt(N) 394 | 395 | #print(root) 396 | print("Attempting to factor {}...".format(N)) 397 | #F,I = size_bound(N) 398 | 399 | print("Generating {}-smooth factor base...".format(B)) 400 | factor_base = find_base(N,B) #generates a B-smooth factor base 401 | #print(factor_base) 402 | 403 | global F 404 | F = len(factor_base) 405 | 406 | print("Looking for {} {}-smooth relations...".format(F+T,B)) 407 | smooth_nums,xlist,indices = find_smooth1(factor_base, N,I) 408 | #finds B-smooth relations, using sieving and Tonelli-Shanks 409 | 410 | print("Found {} smooth relations.".format(len(smooth_nums))) 411 | print(indices) 412 | print(xlist) 413 | print(smooth_nums) 414 | 415 | if len(smooth_nums) < len(factor_base): 416 | return("Not enough smooth numbers. Increase the sieve interval or size of the factor base.") 417 | 418 | print("Building exponent matrix...") 419 | is_square, t_matrix = build_matrix(smooth_nums,factor_base) 420 | #builds exponent matrix mod 2 from relations 421 | 422 | if is_square == True: 423 | x = smooth_nums.index(t_matrix) 424 | factor = gcd(xlist[x]+sqrt(t_matrix),N) 425 | print("Found a square!") 426 | return factor, N/factor 427 | 428 | print("Performing Gaussian Elimination...") 429 | sol_rows,marks,M = gauss_elim(t_matrix) #solves the matrix for the null space, finds perfect square 430 | solution_vec = solve_row(sol_rows,M,marks,0) 431 | 432 | '''vec = [0]*len(smooth_nums) # prints solution vector 433 | for i in solution_vec: 434 | vec[i] = 1 435 | print("Solution vector found: " + str(vec))''' 436 | 437 | print("Solving congruence of squares...") 438 | factor = solve(solution_vec,smooth_nums,xlist,N) #solves the congruence of squares to obtain factors 439 | 440 | for K in range(1,len(sol_rows)): 441 | if (factor == 1 or factor == N): 442 | print("Didn't work. Trying different solution vector...") 443 | solution_vec = solve_row(sol_rows,M,marks,K) 444 | factor = solve(solution_vec,smooth_nums,xlist,N) 445 | else: 446 | print("Found factors!") 447 | return factor, N/factor 448 | 449 | 450 | return("Didn't find any nontrivial factors!") 451 | 452 | 453 | 454 | -------------------------------------------------------------------------------- /Quadratic Sieve-Beta.py: -------------------------------------------------------------------------------- 1 | from math import sqrt, exp, log, log2 2 | import random 3 | from Factor import brent 4 | from MillerRabin import is_probable_prime 5 | from itertools import chain, combinations 6 | import sys 7 | 8 | 9 | def gcd(a, b): # Euclid's algorithm 10 | if b == 0: 11 | return a 12 | elif a >= b: 13 | return gcd(b, a % b) 14 | else: 15 | return gcd(b, a) 16 | 17 | 18 | def isqrt(n): # Newton's method, returns exact int for large squares 19 | x = n 20 | y = (x + 1) // 2 21 | while y < x: 22 | x = y 23 | y = (x + n // x) // 2 24 | return x 25 | 26 | 27 | def nroot(A, n): # nth root algorithm 28 | x = A 29 | delta = 1 30 | while abs(delta) > .01: 31 | # x = (1/n)*(((n-1)*x)+(A/pow(x,n-1))) 32 | delta = (A / pow(x, n - 1) - x) / n 33 | x += delta 34 | return x 35 | 36 | 37 | def nroot2(A, n, i): # f = x^n - A = 0, f' = nx^(n-1) 38 | 39 | x = n 40 | k = 0 41 | for k in range(i): 42 | x = (1 / n) * (((n - 1) * x) + (A / pow(x, n - 1))) 43 | 44 | return x 45 | 46 | 47 | def mprint(M): # prints a matrix in readable form 48 | for row in M: 49 | print(row) 50 | 51 | def latexprint(M): # prints a matrix in latex form 52 | for row in M: 53 | for n in row: 54 | print(str(n) + '&', end='') 55 | print('\\\\') 56 | 57 | 58 | def semi(x): # generate a random semiprime 59 | 60 | n = random.randint(1, x) 61 | k = 0 62 | p = 1 63 | while k < 2: 64 | if is_probable_prime(n): 65 | p *= n 66 | k += 1 67 | n = random.randint(1, x) 68 | print('size {}'.format(len(str(p)))) 69 | return p 70 | 71 | 72 | def factor(N): # completely factors N using Pollard Rho 73 | 74 | def pollard(N, factors): 75 | 76 | rem = N 77 | while True: 78 | if is_probable_prime(rem): 79 | factors.append(rem) 80 | break 81 | 82 | f = brent(rem) 83 | while f == rem: # ensures pollard rho returns a smaller factor 84 | f = brent(rem) 85 | 86 | if f and f < rem: # found a factor 87 | if is_probable_prime(f): # ensure f is prime 88 | # print("Pollard rho (Brent): Prime factor found: %s" % f) 89 | factors.append(f) 90 | rem = rem // f # other factor 91 | else: # factor is composite 92 | # print("Pollard rho (Brent): Non-prime factor found: %s" % f) 93 | rem_f = pollard(f, factors) # recursive part 94 | rem = (rem // f) * rem_f # combines the two remainders 95 | factors.remove(rem_f) # removes tricky duplicate that got appended in 1st if stmt 96 | else: # no more factors found, rem is prime 97 | # print("No (more) small factors found.") 98 | break 99 | 100 | return rem 101 | 102 | factors = [] 103 | pollard(N, factors) 104 | return factors 105 | 106 | 107 | def prime_gen(n): # sieve of Eratosthenes 108 | 109 | isPrime = [False, False] 110 | 111 | for i in range(2, n + 1): # list of markers at each index n 112 | isPrime.append(True) 113 | 114 | for j in range(2, int(n / 2)): # tries gap sizes up to n/2 115 | if isPrime[j] == True: 116 | for k in range(2 * j, n + 1, j): # every multiple of j is composite 117 | isPrime[k] = False 118 | 119 | return [i for i in range(n + 1) if isPrime[i]] 120 | 121 | 122 | def legendre(a, p): # legendre symbol of (a/p) 123 | return pow(a, (p - 1) // 2, p) 124 | 125 | def tonelli(n, p): # tonelli-shanks to solve modular square root: x^2 = n (mod p) 126 | assert legendre(n, p) == 1, "not a square (mod p)" 127 | q = p - 1 128 | s = 0 129 | while q % 2 == 0: 130 | q //= 2 131 | s += 1 132 | if s == 1: 133 | r = pow(n, (p + 1) // 4, p) 134 | return r, p - r 135 | for z in range(2, p): 136 | if p - 1 == legendre(z, p): 137 | break 138 | c = pow(z, q, p) 139 | r = pow(n, (q + 1) // 2, p) 140 | t = pow(n, q, p) 141 | m = s 142 | t2 = 0 143 | while (t - 1) % p != 0: 144 | t2 = (t * t) % p 145 | for i in range(1, m): 146 | if (t2 - 1) % p == 0: 147 | break 148 | t2 = (t2 * t2) % p 149 | b = pow(c, 1 << (m - i - 1), p) 150 | r = (r * b) % p 151 | c = (b * b) % p 152 | t = (t * c) % p 153 | m = i 154 | 155 | return (r, p - r) 156 | 157 | 158 | def size_bound(N): # heuristic for optimal factor base and interval size 159 | 160 | # F = pow(exp(sqrt(log(N)*log(log(N)))),sqrt(2)/4) 161 | B = exp( pow(log(N) * log(log(N)), 0.5)) 162 | 163 | return int(B) 164 | 165 | 166 | def find_base(N, B): 167 | # generates a B-smooth factor base 168 | 169 | factor_base = [] 170 | primes = prime_gen(B) 171 | # print(primes) 172 | 173 | for p in primes: # such that N is a quadratic residue mod p 174 | if legendre(N, p) == 1: 175 | factor_base.append(p) 176 | return (factor_base) 177 | 178 | 179 | def find_base1(N, F): 180 | # generates a factor base of size F 181 | 182 | factor_base = [] 183 | primes = prime_gen(F * 10) # kind of arbitrary 184 | # print(primes) 185 | 186 | for p in primes: 187 | if len(factor_base) == F: 188 | break 189 | if legendre(N, p) == 1: 190 | factor_base.append(p) 191 | return factor_base 192 | 193 | 194 | def find_smooth(N, factor_base, I, root, row_tol, bit_tol): 195 | '''Finds B + row_tol smooth relations. The most recent version utilizes negative intervals, 196 | logarithmic sieving, segmented sieves and the Large Prime variation to maximize efficiency.''' 197 | 198 | def sieve(indices, bits, base_list = None): 199 | '''Run over bits, sequentially adding factor base bits using information from indices''' 200 | 201 | new_indices = [] 202 | for k in range(len(indices)): 203 | starts = indices[k] 204 | p = starts[0] 205 | for i in range(1, 3): # two per prime 206 | 207 | start = starts[i] 208 | if start >= I: 209 | print('start index overshoot,',start) 210 | starts[i] = start - I 211 | continue 212 | 213 | for j in range(start, len(bits), p): 214 | bits[j] += base_bits[k+1] #because 2 was included 215 | #base_list[j].append(factor_base[k+1]) 216 | starts[i] = j + p - I 217 | 218 | new_indices.append(starts) #fresh starts 219 | 220 | return new_indices, bits 221 | 222 | 223 | def find_candidates(n_bits,p_bits,dis_from_center): 224 | '''Filter smooth candidates. Tolerance is adjustable''' 225 | 226 | nx_cands = [] 227 | nsmooth_cands = [] 228 | px_cands = [] 229 | psmooth_cands = [] 230 | 231 | for i in range(I-1, 0, -1): # going backwards to preserve order 232 | x = (root - i) - dis_from_center 233 | if x < 0: # too far negative 234 | continue 235 | 236 | thres = int(log2(abs((x ** 2) - N))) - bit_tol # threshold 237 | #print(x,n_bits[i],nbase_list[i]) 238 | 239 | if abs(n_bits[i]) >= thres: # found B-smooth candidate 240 | nsmooth_cands.append((x ** 2) - N) 241 | nx_cands.append(x) 242 | 243 | for i in range(I): 244 | x = root + i + dis_from_center 245 | thres = int(log2(abs((x ** 2) - N))) - bit_tol # threshold 246 | #print(x,p_bits[i],pbase_list[i]) 247 | 248 | if abs(p_bits[i]) >= thres: # found B-smooth candidate 249 | psmooth_cands.append((x ** 2) - N) 250 | px_cands.append(x) 251 | 252 | 253 | return nsmooth_cands, nx_cands, psmooth_cands, px_cands 254 | 255 | 256 | def verify_smooth(factor_base, smooth_cands, x_cands): 257 | '''verifies smooth relations from candidates''' 258 | 259 | def factor(n, factor_base): # trial division from factor base 260 | 261 | factors = [] 262 | #large_prime_facs = [] 263 | 264 | if n < 0: 265 | factors.append(-1) 266 | n //= -1 267 | 268 | for p in factor_base: 269 | 270 | while n % p == 0: 271 | factors.append(p) 272 | n //= p 273 | if n == 1 or n == -1: 274 | return factors 275 | 276 | else: 277 | return None 278 | 279 | 280 | smooth_nums = [] 281 | factors = [] 282 | x_list = [] 283 | 284 | for i in range(len(smooth_cands)): 285 | 286 | fac = factor(smooth_cands[i], factor_base) 287 | 288 | if fac: 289 | smooth_nums.append(smooth_cands[i]) 290 | factors.append(fac) 291 | x_list.append(x_cands[i]) 292 | 293 | return (smooth_nums, x_list, factors) 294 | 295 | 296 | def verify_smooth_largePrime(factor_base, smooth_cands, x_cands): 297 | '''Large Prime Variation. Factorizations with one larger prime than B are stored, 298 | in the hope of combining with another to yield a usable exponent vector.''' 299 | 300 | def factor(n, factor_base): 301 | '''Trial division from factor base. 302 | Modern versions use sieving instead.''' 303 | 304 | factors = [] 305 | #large_prime_facs = [] 306 | 307 | if n < 0: 308 | factors.append(-1) 309 | n //= -1 310 | 311 | for p in factor_base: 312 | 313 | while n % p == 0: 314 | factors.append(p) 315 | n //= p 316 | if n == 1 or n == -1: 317 | return False, factors 318 | 319 | elif n < B**2: #large prime variation 320 | #print(n) 321 | #print(is_probable_prime(n)) 322 | factors.append(n) 323 | return True, factors 324 | else: 325 | return None, None 326 | 327 | def largePrime(large_p_relations): 328 | 'combine large prime cycles' 329 | 330 | large_p_relations = sorted(large_p_relations) 331 | #print(large_p_relations) 332 | 333 | while len(large_p_relations)>1: 334 | 335 | shared_primes = [] 336 | #print(len(large_p_relations)) 337 | shared_primes.append(large_p_relations.pop(0)) 338 | 339 | while shared_primes[0][0][0] == large_p_relations[0][0][0]: 340 | shared_primes.append(large_p_relations.pop(0)) 341 | #print(shared_primes) 342 | 343 | if len(shared_primes) == 1: # no matches 344 | continue 345 | 346 | else: #create and combine exponent matrices 347 | 348 | '''if shared_primes[0][0] is in large_p_list: 349 | 350 | large_p_list.append(shared_primes[0][0])''' 351 | '''for relation in shared_primes: 352 | exp_vector = make_vector(list(reversed(relation[0])),factor_base)''' 353 | #sums = list(combinations(shared_primes,2)) 354 | sums = shared_primes 355 | #print(sums) 356 | for i in range(1,len(sums)): 357 | 358 | '''del sums[0][0][0] 359 | del sums[i][0][0]''' 360 | factors.append(sums[0][0]+sums[i][0]) 361 | smooth_nums.append(sums[0][1]*sums[i][1]) 362 | x_list.append(sums[0][2]*sums[i][2]) 363 | 364 | print(sums[0][0]+sums[i][0]) 365 | print(sums[0][1]*sums[i][1],sums[0][2]*sums[i][2],'\n') 366 | 367 | '''class largePrime: 368 | def __init__(self,prime): 369 | self.prime = prime''' 370 | 371 | smooth_nums = [] 372 | factors = [] 373 | x_list = [] 374 | large_p_relations = [] 375 | 376 | for i in range(len(smooth_cands)): 377 | is_largePrime, fac = factor(smooth_cands[i], factor_base) 378 | if is_largePrime: 379 | large_p_relations.append([list(reversed(fac)),smooth_cands[i],x_cands[i]]) 380 | #large_primes.append(fac[-1]) 381 | elif fac: 382 | smooth_nums.append(smooth_cands[i]) 383 | factors.append(fac) 384 | x_list.append(x_cands[i]) 385 | 386 | largePrime(large_p_relations) 387 | 388 | return (smooth_nums, x_list, factors) 389 | 390 | 391 | '''Find smooth numbers''' 392 | 393 | base_bits = [round(log2(p)) for p in factor_base] 394 | p_indices = [] 395 | n_indices = [] 396 | 397 | '''Initialize starting indices. There are two roots per factor, so each factor 398 | is stored with two indices, in a 3-tuple.''' 399 | 400 | for i in range(1,len(base_bits)): # 2 is ignored 401 | 402 | p = factor_base[i] 403 | mod_roots = tonelli(N, p) # two roots 404 | p_tuple = [p] # p_ = positive, refers to interval direction 405 | n_tuple = [p] 406 | 407 | for r in mod_roots: 408 | start = ((r - root) % p) # idk why 409 | p_tuple.append(start) 410 | n_tuple.append(abs(start - p)) 411 | 412 | p_indices.append(p_tuple) 413 | n_indices.append(n_tuple) 414 | 415 | smooth_nums = [] 416 | x_list = [] 417 | factors = [] 418 | 419 | dis_from_center = 0 420 | 421 | '''Sieve. Repeat sieving if necessary, increasing distance from center''' 422 | 423 | while len(smooth_nums) < len(factor_base) + row_tol: 424 | 425 | #print('we have', len(smooth_nums), 'extending interval...') 426 | 427 | p_bits = [0 for x in range(I)] 428 | n_bits = [0 for x in range(I)] 429 | '''pbase_list = [[] for x in range(I)] 430 | nbase_list = [[] for x in range(I)] 431 | print(p_indices) 432 | print(n_indices)''' 433 | 434 | p_indices, p_bits = sieve(p_indices, p_bits) 435 | n_indices, n_bits = sieve(n_indices, n_bits) 436 | 437 | nsmooth_cands, nx_cands, psmooth_cands, px_cands = find_candidates(n_bits,p_bits,dis_from_center) 438 | print(len(nsmooth_cands)+len(psmooth_cands),'found') 439 | #print('verifying...') 440 | n_smooths, n_xs, n_factors = verify_smooth(factor_base, nsmooth_cands, nx_cands) 441 | p_smooths, p_xs, p_factors = verify_smooth(factor_base, psmooth_cands, px_cands) 442 | 443 | '''Appending smooth relations in numeric order, optional''' 444 | smooth_nums += p_smooths 445 | x_list += p_xs 446 | factors += p_factors 447 | 448 | smooth_nums = n_smooths + smooth_nums #negatives go before! 449 | x_list = n_xs + x_list 450 | factors = n_factors + factors 451 | 452 | dis_from_center += I 453 | 454 | print('total interval size of {}'.format((dis_from_center)*2)) 455 | 456 | return smooth_nums, x_list, factors 457 | 458 | 459 | def make_vector(n_factors,factor_base): 460 | '''turns factorization into an exponent vector mod 2''' 461 | 462 | exp_vector = [0] * (len(factor_base)) 463 | # print(n,n_factors) 464 | for j in range(len(factor_base)): 465 | if factor_base[j] in n_factors: 466 | exp_vector[j] = (exp_vector[j] + n_factors.count(factor_base[j])) % 2 467 | return exp_vector 468 | 469 | def transpose(matrix): 470 | '''transpose matrix so columns become rows, makes list comp easier to work with. 471 | Alternatively use Numpy column manipulations''' 472 | 473 | new_matrix = [] 474 | for i in range(len(matrix[0])): 475 | new_row = [] 476 | for row in matrix: 477 | new_row.append(row[i]) 478 | new_matrix.append(new_row) 479 | return (new_matrix) 480 | 481 | 482 | def build_matrix(factor_base, smooth_nums, factors): 483 | '''builds matrix from exponent vectors mod 2 from smooth numbers''' 484 | 485 | M = [] 486 | factor_base.insert(0, -1) 487 | 488 | for i in range(len(smooth_nums)): 489 | 490 | exp_vector = make_vector(factors[i],factor_base) 491 | # print(n_factors, exp_vector) 492 | 493 | if 1 not in exp_vector: # search for squares 494 | return True, (smooth_nums[i]) 495 | else: 496 | pass 497 | 498 | M.append(exp_vector) 499 | 500 | M = transpose(M) 501 | # mprint(M) 502 | return (False, M) 503 | 504 | 505 | def gauss_elim(M): 506 | '''reduced form of gaussian elimination, finds rref and reads off the nullspace 507 | https://www.cs.umd.edu/~gasarch/TOPICS/factoring/fastgauss.pdf''' 508 | 509 | # M = optimize(M) 510 | marks = [False] * len(M[0]) 511 | 512 | for i in range(len(M)): # do for all rows 513 | row = M[i] 514 | # print(row) 515 | 516 | for num in row: # search for pivot 517 | if num == 1: 518 | # print("found pivot at column " + str(row.index(num)+1)) 519 | j = row.index(num) # column index 520 | marks[j] = True 521 | 522 | for k in chain(range(0, i), range(i + 1, len(M))): # search for other 1s in the same column 523 | if M[k][j] == 1: 524 | for i in range(len(M[k])): 525 | M[k][i] = (M[k][i] + row[i]) % 2 526 | break 527 | 528 | M = transpose(M) 529 | # print(marks) 530 | # mprint(M) 531 | 532 | sol_rows = [] 533 | for i in range(len(marks)): # find free columns (which have now become rows) 534 | if not marks[i]: # found free row 535 | sol_rows.append([M[i], i]) 536 | 537 | if not sol_rows: 538 | print("No solution found. Need more smooth numbers.") 539 | sys.exit() 540 | 541 | print("Found {} potential solutions.\n".format(len(sol_rows))) 542 | #print(sol_rows) 543 | return sol_rows, marks, M 544 | 545 | 546 | def solve_row(sol_rows, M, marks, K=0): 547 | '''Find linear dependencies and create solution vector''' 548 | 549 | solution_vec, indices = [], [] 550 | free_row = sol_rows[K][0] # may be multiple K 551 | for i in range(len(free_row)): 552 | if free_row[i] == 1: 553 | indices.append(i) 554 | 555 | for r in range(len(M)): # rows with 1 in the same column will be dependent 556 | for i in indices: 557 | if M[r][i] == 1 and marks[r]: 558 | solution_vec.append(r) 559 | break 560 | #print(solution_vec) 561 | # print("Found linear dependencies at rows "+ str(solution_vec)) 562 | solution_vec.append(sol_rows[K][1]) 563 | return (solution_vec) 564 | 565 | 566 | def solve(solution_vec, smooth_nums, factors, x_list, N, factor_base): 567 | '''Solves the congruence of squares''' 568 | 569 | solution_nums = [smooth_nums[i] for i in solution_vec] 570 | #sol_facs = [factors[i] for i in solution_vec] 571 | x_nums = [x_list[i] for i in solution_vec] 572 | 573 | '''for i in range(len(solution_vec)): 574 | print(x_nums[i],solution_nums[i],sol_facs[i])''' 575 | 576 | '''residues = [] 577 | 578 | for i in range(len(factor_base)): 579 | f = factor_base[i] 580 | exponent = 0 581 | for row in factors: #deal with smaller numbers 582 | e = row.count(f) 583 | exponent += e 584 | 585 | #print(exponent) 586 | res = pow(f,exponent,N) 587 | residues.append(res) 588 | 589 | a = 1 590 | for r in residues: 591 | a *= r 592 | a = a % N''' 593 | 594 | b = 1 595 | for n in x_nums: 596 | b *= n 597 | 598 | Asquare = 1 599 | for n in solution_nums: 600 | Asquare *= n 601 | a = isqrt(Asquare) 602 | assert a**2 == Asquare, 'not square' 603 | #print(str(a)+"^2 == "+str(b)+"^2 mod "+str(N)) 604 | 605 | factor = gcd(abs(b - a), N) 606 | print(factor) 607 | return factor 608 | 609 | 610 | def QS(N, b = None, I = None): 611 | '''Single polynomial version of quadratic sieve, smoothness bound b and sieve interval I. 612 | Estimation is provided if unknown. Matrix becomes slow around B = 50000''' 613 | 614 | assert not is_probable_prime(N), "prime" 615 | 616 | for power in range(2, int(log2(N))): # test for prime powers 617 | r = int(1000 * pow(N, 1 / power)) // 1000 618 | if pow(r, power) == N: 619 | print('found root') 620 | return r 621 | 622 | print("Data Collection Phase...") 623 | # set row_tol for extra solutions, bit_tol for sieve fudge factor 624 | root, row_tol, bit_tol = int(sqrt(N)), 0, 20 625 | global B 626 | B = b 627 | 628 | if not B: # automatic parameter estimation 629 | B = size_bound(N) 630 | I = B 631 | print('Estimated B =', B, 'I =', I, '\n') 632 | elif not I: 633 | I = B 634 | 635 | factor_base = find_base(N, B) 636 | F = len(factor_base) 637 | print(F, 'factors in factor base') 638 | 639 | print("\nSearching for {}+{} B-smooth relations...".format(F, row_tol)) 640 | print('Sieving for candidates...') 641 | smooth_nums, x_list, factors = find_smooth(N, factor_base, I, root, row_tol, bit_tol) 642 | 643 | if len(smooth_nums) < F: 644 | return ("Error: not enough smooth numbers") 645 | 646 | print("\nFound {} relations.".format(len(smooth_nums))) 647 | 648 | if len(smooth_nums)-100 > F: #reduce for smaller matrix 649 | print('trimming smooth relations...') 650 | del smooth_nums[F+row_tol:] 651 | del x_list[F+row_tol:] 652 | del factors[F+row_tol:] 653 | print(len(smooth_nums)) 654 | 655 | '''for i in range(len(x_list)): 656 | print(x_list[i], smooth_nums[i], factors[i])''' 657 | 658 | print("\nMatrix Phase. Building exponent matrix...") 659 | is_square, t_matrix = build_matrix(factor_base, smooth_nums, factors) 660 | 661 | if is_square: 662 | print("Found a square!") 663 | x = smooth_nums.index(t_matrix) 664 | factor = (gcd(x_list[x] + isqrt(t_matrix), N)) 665 | return factor, N / factor 666 | 667 | print("\nPerforming Gaussian Elimination...") 668 | sol_rows, marks, M = gauss_elim(t_matrix) 669 | 670 | print('Finding linear dependencies...') 671 | solution_vec = solve_row(sol_rows, M, marks, 0) 672 | factor_base.remove(-1) 673 | 674 | print("Solving congruence of squares...") 675 | factor = solve(solution_vec, smooth_nums, factors, x_list, N, factor_base) 676 | 677 | for K in range(1, len(sol_rows)): 678 | if (factor == 1 or factor == N): 679 | print("Trivial. Trying again...") 680 | solution_vec = solve_row(sol_rows, M, marks, K) 681 | factor = solve(solution_vec, smooth_nums, factors, x_list, N, factor_base) 682 | else: 683 | print("Success!") 684 | return factor, N // factor 685 | 686 | return 'Fail. Increase B, I or T.' 687 | 688 | 689 | __version__ = 1.3 690 | --------------------------------------------------------------------------------