Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pwang00
GitHub Repository: pwang00/Cryptographic-Attacks
Path: blob/master/Public Key/RSA/coron.sage
336 views
1
import itertools
2
3
def coppersmith_bivariate(p, X, Y, k = 2, i_0 = 0, j_0 = 1, debug=True):
4
"""
5
Implements Coron's simplification of Coppersmith's algorithm in the bivariate case:
6
https://www.iacr.org/archive/crypto2007/46220372/46220372.pdf
7
8
Per the paper, Coron's simplification relies on the following result:
9
10
Given some p(x, y) in Z[x, y], we can fix some arbitrary integer n and construct a lattice of polynomials
11
that are multiples of p(x, y) and n, and then reduce this lattice to obtain a polynomial h(x, y) with small coefficients such that
12
if h(x_0, y_0) = 0 (mod n) for some arbitrary integer n, then h(x_0, y_0) = 0 over Z holds as well.
13
14
:param p: bivariate polynomial p(x, y) in Z[x, y]
15
:param X: Bound for root x_0 of p
16
:param Y: Bound for root y_0 of p
17
:param k: Determines size of the M matrix and the corresponding lattice L_2.
18
:param i_0: index such that 0 <= i_0 <= deg(p), used to generate monomials x^(i + i_0)*y^(j + j_0)
19
:param j_0: index such that 0 <= j_0 <= deg(p), used to generate monomials x^(i + i_0)*y^(j + j_0)
20
:param debug: Turns on debugging information
21
:return: The small roots x_0, y_0 of h(x, y) such that |x_0| <= X, |y_0| <= Y, and h(x_0, y_0) = 0 over Z
22
"""
23
if len(p.variables()) != 2:
24
raise ValueError("Given polynomial is not bivariate.")
25
26
# We want to make sure that XY < W = max_ij(p_ij x^i y^j), otherwise there may not be a solution.
27
d = max(p.degree(x), p.degree(y))
28
W = 0
29
30
if debug:
31
print(f"Attempting to find small roots for the given polynomial over Z...")
32
print(f"With parameters k = {k}, i_0 = {i_0}, j_0 = {j_0}")
33
34
# Iterate through all the monomials of p to calculate W
35
for term in p:
36
p_ij, m = term
37
i, j = m.degree(x), m.degree(y)
38
W = max(W, p_ij * X^i * Y^j)
39
40
if debug and X * Y > W^(2/(3*d)):
41
print("Warning: XY > W^(2/(3*d)); a solution may not be found.")
42
43
44
prods = list(itertools.product(range(k), range(k)))[::-1]
45
prods_kd = list(itertools.product(range(k + d), range(k + d)))[::-1]
46
terms = sorted([x^(i + i_0)*y^(j + j_0) for i, j in prods], reverse=True)
47
48
# Generates a temporary polynomial via expanding (1 + x + x^2 + ... + x^n)(1 + y + y^2 + ... + y^n)
49
# Later filters out the monomial terms whose degrees in x and y independently exceed
50
# the highest order term across all x^(i + i_0)*y^(j + j_0).
51
f = sum(x^i for i in range(terms[0].degree() // 2 + 2))
52
f *= f(x=y)
53
54
highest_order = terms[0]
55
d2 = max(highest_order.degree(x), highest_order.degree(y))
56
57
# Make sure the left block of M corresponds to the coefficients of
58
# the monomials that we care about; the ones we do are stored in `terms`
59
# and the others are stored in `rest`.
60
# We restrict the maximum degree independently in x, y of all terms to be less than that
61
# of the highest order term across all x^(i + i_0)*y^(j + j_0).
62
rest = [t for t in list(zip(*list(f)))[1] if max(t.degree(x), t.degree(y)) <= d2 and t not in terms]
63
s_terms = terms + rest
64
65
# Builds the matrix S and calculates n = |det S|.
66
X_dim, Y_dim = k^2, k^2
67
S = Matrix(ZZ, X_dim, Y_dim)
68
69
# Puts the coefficients corresponding to each monomial in order for every row of S.
70
for r, (a, b) in enumerate(prods):
71
s_ab = x^a * y^b * p
72
coeffs, mons = zip(*list(s_ab))
73
s_dict = {k: v for k, v in zip(mons, coeffs)}
74
row = vector([s_dict[t] if t in s_dict else 0 for t in terms])
75
S[r] = row
76
77
n = det(S)
78
79
# Builds the matrix M as described in the paper, which is k^2 + (k + d)^2 x (k + d)^2
80
# The first k^2 rows of M consist of the coefficients of the polynomials s_ab(xX, yY) where
81
# 0 <= a, b <= d.
82
X_dim, Y_dim = k^2 + (k + d)^2, (k + d)^2
83
84
# Puts the coefficients corresponding to each monomial in order for every row of S.
85
M = Matrix(ZZ, X_dim, Y_dim)
86
for r, (a, b) in enumerate(prods):
87
s_ab = x^a * y^b * p
88
coeffs, mons = zip(*list(s_ab))
89
s_dict = {k: v for k, v in zip(mons, coeffs)}
90
row = vector([s_dict[t] * t(x=X, y=Y) if t in s_dict else 0 for t in s_terms])
91
M[r] = row
92
93
# The next (k + d)^2 rows consist of the coefficients of the polynomials r_ab where
94
# 0 <= a, b <= k + d. Again, the coefficients for each r_ab are inserted in order corresponding
95
# To each monomial term.
96
97
for r, (i, j) in zip(range(k^2, X_dim), prods_kd):
98
r_ab = x^i * y^j * n
99
coeffs, mons = zip(*list(r_ab))
100
r_dict = {k: v for k, v in zip(mons, coeffs)}
101
row = vector([n * t(x=X, y=Y) if t in r_dict else 0 for t in s_terms])
102
M[r] = row
103
104
# Coron describes a triangularization algorithm to triangularize M, but claims that obtaining the
105
# Hermite normal form of M works as well, so we do the latter since Sage already has it implemented.
106
M = M.hermite_form()
107
108
# As mentioned above, `rest` contains the monomials other than the k^2 ones we chose at the beginning.
109
l = len(rest)
110
111
# Performs LLL on L_2
112
L_2 = M[list(range(k^2, k^2 + l)), list(range(k^2, k^2 + l))].LLL()
113
114
# The first row of the LLL-reduced L_2 contains a short vector of coefficients b_1
115
# corresponding to the coefficients of a polynomial h(x, y) that is not a multiple of p(x, y).
116
# Irreducibility of p(x, y) implies that p(x, y) and h(x, y) are algebraically independent
117
# and that they share a root (x_0, y_0).
118
119
# Builds h(x, y) by summing the products of monomials and their coefficient terms, and dividing out by
120
# the extra factors of X^i*Y^j.
121
h = sum(coeff * term // (X^term.degree(x) * Y^term.degree(y)) for (coeff, term) in zip(L_2[0], rest))
122
123
# Takes the resultant of h(x, y) and p(x, y).
124
q = h.resultant(p, variable=y)
125
126
# Obtains the roots x_i of q as a univariate polynomial in x over Z if they exist. Sage implements this via .roots()
127
# Then finds roots for q(x_i, y) as a univariate polynomial in y over Z if they exist.
128
roots_x = q.univariate_polynomial().roots(multiplicities=False)
129
130
roots_y = []
131
for x_0 in roots_x:
132
y_0 = p(x=x_0).univariate_polynomial().roots(multiplicities=False)
133
roots_y.append(y_0[0] if y_0 else None)
134
135
if debug and len(roots_x) > 0 and len(roots_y) > 0:
136
print(f"Found roots for p: x = {roots_x}, y = {roots_y}.")
137
138
return roots_x, roots_y
139
140
141
if __name__ == "__main__":
142
143
# Factors N = pq given the most significant bits of p and q.
144
P.<x, y> = PolynomialRing(ZZ)
145
prime_size = 512
146
p = random_prime(1<<prime_size, proof=False)
147
q = random_prime(1<<prime_size, proof=False)
148
149
N = p * q
150
151
# Lower bits of p
152
lower = 300
153
bits = prime_size - lower
154
mask = 1 << bits
155
# How many MSBs we want to preserve
156
# p // mask * mask zeros out log_2(mask) number of LSBs of p
157
158
p_0 = (p // mask) * mask
159
q_0 = (N // p_0 // mask) * mask
160
161
X, Y = mask << 1, mask << 1
162
poly = (x + p_0) * (y + q_0) - N
163
164
print("--Given arguments--")
165
print(f"Size of primes: {prime_size}")
166
print(f"Number of known MSBs of p: {bits}")
167
print(f"N = {N}")
168
print("\n")
169
res = coppersmith_bivariate(poly, X, Y, k=4)
170
print("\n")
171
if not res:
172
raise ValueError("No roots found.")
173
174
x_s, y_s = res
175
p_r, q_r = 0, 0
176
177
# We need to check every combination of roots to find one such that (p_0 * 2^k + x_0)(q_0 * 2^k + y_0) = N.
178
179
for x_0, y_0 in itertools.product(x_s, y_s):
180
p_r = p_0 + x_0
181
q_r = q_0 + y_0
182
183
if p_r * q_r == N:
184
print(f"Successfully factored N!")
185
print(f"p = {p_r}")
186
print(f"q = {q_r}")
187
break
188
189
assert p_r * q_r == N, "Recovered values were incorrect."
190
191
192
193
194