from math import log, exp, sqrt, floor, gcd from sympy import primerange, prevprime, nextprime from sympy.ntheory.residue_ntheory import legendre_symbol, sqrt_mod from tqdm.notebook import tqdm, trange
n = 1234268228312430759578090015472355712114804731217710966738223 M = 234722 bound = 2 * int(exp(0.5 * sqrt(0.707 * log(n) * log(log(n))))) prime_max = prevprime(bound + 1) thresh = log(M * sqrt(2*n), 10) - log(prime_max, 10) * 2 # filter out too small primes factor_base = [2] + [p for p in primerange(3 , prime_max + 1) if legendre_symbol(n, p) == 1]
# calc sqrt_mod(n, p^i) & log(p) sieve_primes = [] mod_root = {} log_p = {} for prime in factor_base: log_p[prime] = log(prime,10) if prime > 2: # int(2 * thresh): sieve_primes.append(prime) r = sqrt_mod(n, prime) roots = [r] q = prime * prime while q < M: # find p^n via Hensal Lifting r = (r - (r * r - n) * pow(r + r, -1, q)) % q assert (r * r) % q == n % q roots.append(r) q *= prime mod_root[prime] = roots bound, len(factor_base), len(sieve_primes), factor_base[:10], sieve_primes[:10]
# A = next(A_generator(ideal_A, 10)) defQ_generator(ideal_q): A,B,C = 0,0,0 defQ(a): return A * a ** 2 + 2 * B * a + C for q in q_generator(ideal_q): A = q * q b = sqrt_mod(n, A) if b isNone: continue B = (b + (n - b*b) * pow(b+b, -1, q)) % A C = (B * B - n) // A yield q,A,B,C,Q
deflist_prod(a): whilelen(a) > 1: a = [m * n for m, n inzip(a[::2], a[1::2] + [1])] return a[0]
with tqdm(Q_generator(ideal_q), total=len(factor_base)+1, desc="Finding No.0 Q(a)") as pbar: for q,A,B,C,Q in Q_generator(ideal_q): poly_cnt += 1 pbar.set_description(f"Finding No.{poly_cnt} Q(a)") # seive init fudge = sum([log_p[factor_base[i]] // (factor_base[i] - 1) for i inrange(len(factor_base) - len(sieve_primes))]) # print(fudge) V = [fudge for _ inrange(-M, M)] # V = {a:0 for a in range(-M, M)} # sieve for prime factors for prime in sieve_primes: lg_p = log_p[prime]
e = 0# mod prime^(e+1) pe = prime # value of prime^(e+1)
while pe < M: inv_A = pow(A, -1, pe)
sroot_a = ((mod_root[prime][e] - B) * inv_A) % pe sroot_b = ((pe - mod_root[prime][e] - B) * inv_A) % pe
# print(mod_root[prime][e], pe - mod_root[prime][e], sqrt_mod(n, pe, True))
# assert Q(sroot_a) % pe == 0 # assert Q(sroot_b) % pe == 0
e += 1 pe *= prime # goto p^e+1 # check for smooths for a inrange(-M,M): if V[a] < thresh: continue factor_vec = set() factor_square = [] value = Q(a) if value < 0: factor_vec.add(-1) value = -value for prime in factor_base: if prime > value: break while value % prime == 0: if prime in factor_vec: # it's a square factors! mark it. factor_square.append(prime) factor_vec ^= {prime} value //= prime if value == 1: # A Smooth Number # print(f"A Smooth Number found: {Q(a)}: with factors {factor_vec}") smooth.append((factor_vec, (factor_square, (A * a + B), q))) used_prime |= factor_vec pbar.set_postfix({"total_smooth":len(smooth), "partial": num_partial, "partial_wait_list": len(partial), "used_prime": len(used_prime)}) pbar.update() elif value in partial: # combine two partials to make a xor smooth # if we find the other pair, we can apply a xor to it and make the multipy as a smooth number # it's ok for one partial pairing with more than one partial pair_vec, pair_vals = partial[value] factor_square.extend(list(factor_vec & pair_vec)) factor_square.append(value) factor_vec ^= pair_vec # print(f"A partial Smooth Number found: {Q(a)}: with factors {factor_vec}") smooth.append((factor_vec, (factor_square + pair_vals[0], (A*a + B) * pair_vals[1], q * pair_vals[2]))) used_prime |= factor_vec num_partial += 1 pbar.set_postfix({"total_smooth":len(smooth), "partial": num_partial, "partial_wait_list": len(partial), "used_prime": len(used_prime)}) pbar.update() else: # save partial partial[value] = (factor_vec, (factor_square, A*a + B, q)) prev_num_smooth = num_smooth num_smooth = len(smooth) num_used_prime = len(used_prime)
if num_smooth > num_used_prime and num_smooth > prev_num_smooth: # set up bit fields for gaussian elimination masks = [] mask = 1 bit_fields = [0] * num_used_prime for vec, vals in smooth: masks += [mask] i = 0 for p in used_prime: if p in vec: bit_fields[i] |= mask i += 1 mask += mask # row echelon form col_offset = 0 null_cols = [] for col inrange(num_smooth): pivot = col - col_offset == num_used_prime or bit_fields[col - col_offset] & masks[col] == 0 for row inrange(col + 1 - col_offset, num_used_prime): if bit_fields[row] & masks[col]: if pivot: bit_fields[col - col_offset], bit_fields[row] = bit_fields[row], bit_fields[ col - col_offset] pivot = False else: bit_fields[row] ^= bit_fields[col - col_offset] if pivot: null_cols += [col] col_offset += 1 # reduced row echelon form for row inrange(num_used_prime): # lowest set bit mask = bit_fields[row] & -bit_fields[row] for up_row inrange(row): if bit_fields[up_row] & mask: bit_fields[up_row] ^= bit_fields[row] # check for non-trivial congruencies for col in null_cols: all_vec, (lh, rh, rA) = smooth[col] lhs = lh # sieved values (left hand side) rhs = [rh] # sieved values - n (right hand side) rAs = [rA] # root_As (cofactor of lhs) i = 0 for field in bit_fields: if field & masks[col]: vec, (lh, rh, rA) = smooth[i] lhs += list(all_vec & vec) + lh all_vec ^= vec rhs += [rh] rAs += [rA] i += 1 factor = gcd(list_prod(rAs) * list_prod(lhs) - list_prod(rhs), n) if1 < factor < n: break else: print('none found.') continue break
print('factors found:') print(factor, 'x', n // factor) # cnt
Thresh: 25.42751876372442
factors found:
1231768382920384328173284372769 x 1002029476829174312039487628367