Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pwang00
GitHub Repository: pwang00/Cryptographic-Attacks
Path: blob/master/Public Key/Factoring/cm_factor.sage
336 views
1
import sys
2
from utils import *
3
4
sys.setrecursionlimit(100000000)
5
6
def cm_factor(N, D, B=32, debug=True):
7
"""
8
Implements a simplified version of Cheng's 4p - 1 elliptic curve complex multiplication based factorization algorithm.
9
Targets moduli where one (or many) prime factors satisfies the equality 4p_i - 1 = D * s^2, where D is a squarefree integer
10
and s is a randomly generated one between an upper and lower bound.
11
12
Original paper title: I want to break square-free: The 4p - 1 factorization method and its RSA backdoor viability
13
Link: https://crocs.fi.muni.cz/_media/public/papers/2019-secrypt-sedlacek.pdf
14
15
:param N: integer to be factored
16
:param D: squarefree integer satisfying 4p - 1 = D * s^2
17
:param B: number of iterations to run the algorithm for
18
:param debug: switches debugging information on/off
19
:return: a tuple corresponding to p, q, or failure (-1)
20
"""
21
22
# If D is not squarefree then we terminate immediately.
23
assert D.is_squarefree(), "D must be squarefree."
24
25
# Computes the -Dth Hilbert polynomial modulo N and quotient ring Q = Z_N[x] / <H_{-D, N}>
26
27
Z_N = Zmod(N)
28
P = Z_N[x]
29
H = P(hilbert_class_polynomial(-D))
30
Q = P.quotient_ring(H)
31
32
# j is the equivalence class corresponding to [X] in Q.
33
j = Q(x)
34
35
# The paper claims that we can treat both 1728 - j and H as polynomials in Z_N[X] and calculate the inverse of
36
# 1728 - j and H via egcd. This doesn't quite work off the shelves, so we instead accomplish this by treating
37
# 1728 - j as an element of Q = Z_N[x] / <H_{-D, n}> and lifting it back into the base ring.
38
# Sage implements this via the .lift() method.
39
40
if debug:
41
print("Calculating inverse mod of 1728 - j with H...")
42
43
try:
44
k = j * polynomial_inv_mod((1728 - j).lift(), H)
45
except ValueError as e:
46
r = gcd(int(e.args[1].lc()), N)
47
return int(r), int(N // r)
48
49
# Constructs an elliptic curve described by the equation y^2 = x^3 + ax + b where a = 3k and b = 2k over Q.
50
# This is so we can calculate the division polynomial psi_n(a, b, x_i) later.
51
E = EllipticCurve(Q, [3*k, 2*k])
52
53
for i in range(B):
54
55
# Obtains a random element from Z_N for the division polynomial.
56
x_i = Z_N.random_element()
57
58
if debug:
59
print("Calculating division polynomial with x_i...")
60
61
# Calculates the division polynomial modulo n: psi_n(a, b, x_i)
62
z = E.division_polynomial(N, x=Q(x_i))
63
64
# If our egcd implementation throws an exception, then we know that the r polynomial during the computation
65
# has no inverse; we return this polynomial in our exception and then take the gcd of its leading coefficient with N.
66
# Otherwise we take the gcd of d and N normally.
67
try:
68
d, _, _ = polynomial_egcd(z.lift(), H)
69
except ValueError as e:
70
r = gcd(int(e.args[1].lc()), N)
71
return int(r), int(N // r)
72
73
r = gcd(int(d), N)
74
75
# Ensure that the factorization is nontrivial: i.e. r != 1 and r != N
76
if 1 < r < N:
77
return r, N // r
78
79
return -1
80
81
if __name__ == "__main__":
82
D = 419
83
# Generates a modulus N = pq where 4p - 1 = Ds^2.
84
85
print("Generating modulus...")
86
p = generate_cm_prime(D)
87
q = random_prime(1<<1024, proof=False)
88
89
N = p * q
90
print("Factoring...")
91
r, s = cm_factor(N, D)
92
assert r * s == N
93
print(f"Factorization successful! N = {r} * {s}")
94