Path: blob/master/Public Key/RSA/coron.sage
336 views
import itertools12def coppersmith_bivariate(p, X, Y, k = 2, i_0 = 0, j_0 = 1, debug=True):3"""4Implements Coron's simplification of Coppersmith's algorithm in the bivariate case:5https://www.iacr.org/archive/crypto2007/46220372/46220372.pdf67Per the paper, Coron's simplification relies on the following result:89Given some p(x, y) in Z[x, y], we can fix some arbitrary integer n and construct a lattice of polynomials10that are multiples of p(x, y) and n, and then reduce this lattice to obtain a polynomial h(x, y) with small coefficients such that11if 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.1213:param p: bivariate polynomial p(x, y) in Z[x, y]14:param X: Bound for root x_0 of p15:param Y: Bound for root y_0 of p16:param k: Determines size of the M matrix and the corresponding lattice L_2.17:param i_0: index such that 0 <= i_0 <= deg(p), used to generate monomials x^(i + i_0)*y^(j + j_0)18:param j_0: index such that 0 <= j_0 <= deg(p), used to generate monomials x^(i + i_0)*y^(j + j_0)19:param debug: Turns on debugging information20: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 Z21"""22if len(p.variables()) != 2:23raise ValueError("Given polynomial is not bivariate.")2425# We want to make sure that XY < W = max_ij(p_ij x^i y^j), otherwise there may not be a solution.26d = max(p.degree(x), p.degree(y))27W = 02829if debug:30print(f"Attempting to find small roots for the given polynomial over Z...")31print(f"With parameters k = {k}, i_0 = {i_0}, j_0 = {j_0}")3233# Iterate through all the monomials of p to calculate W34for term in p:35p_ij, m = term36i, j = m.degree(x), m.degree(y)37W = max(W, p_ij * X^i * Y^j)3839if debug and X * Y > W^(2/(3*d)):40print("Warning: XY > W^(2/(3*d)); a solution may not be found.")414243prods = list(itertools.product(range(k), range(k)))[::-1]44prods_kd = list(itertools.product(range(k + d), range(k + d)))[::-1]45terms = sorted([x^(i + i_0)*y^(j + j_0) for i, j in prods], reverse=True)4647# Generates a temporary polynomial via expanding (1 + x + x^2 + ... + x^n)(1 + y + y^2 + ... + y^n)48# Later filters out the monomial terms whose degrees in x and y independently exceed49# the highest order term across all x^(i + i_0)*y^(j + j_0).50f = sum(x^i for i in range(terms[0].degree() // 2 + 2))51f *= f(x=y)5253highest_order = terms[0]54d2 = max(highest_order.degree(x), highest_order.degree(y))5556# Make sure the left block of M corresponds to the coefficients of57# the monomials that we care about; the ones we do are stored in `terms`58# and the others are stored in `rest`.59# We restrict the maximum degree independently in x, y of all terms to be less than that60# of the highest order term across all x^(i + i_0)*y^(j + j_0).61rest = [t for t in list(zip(*list(f)))[1] if max(t.degree(x), t.degree(y)) <= d2 and t not in terms]62s_terms = terms + rest6364# Builds the matrix S and calculates n = |det S|.65X_dim, Y_dim = k^2, k^266S = Matrix(ZZ, X_dim, Y_dim)6768# Puts the coefficients corresponding to each monomial in order for every row of S.69for r, (a, b) in enumerate(prods):70s_ab = x^a * y^b * p71coeffs, mons = zip(*list(s_ab))72s_dict = {k: v for k, v in zip(mons, coeffs)}73row = vector([s_dict[t] if t in s_dict else 0 for t in terms])74S[r] = row7576n = det(S)7778# Builds the matrix M as described in the paper, which is k^2 + (k + d)^2 x (k + d)^279# The first k^2 rows of M consist of the coefficients of the polynomials s_ab(xX, yY) where80# 0 <= a, b <= d.81X_dim, Y_dim = k^2 + (k + d)^2, (k + d)^28283# Puts the coefficients corresponding to each monomial in order for every row of S.84M = Matrix(ZZ, X_dim, Y_dim)85for r, (a, b) in enumerate(prods):86s_ab = x^a * y^b * p87coeffs, mons = zip(*list(s_ab))88s_dict = {k: v for k, v in zip(mons, coeffs)}89row = vector([s_dict[t] * t(x=X, y=Y) if t in s_dict else 0 for t in s_terms])90M[r] = row9192# The next (k + d)^2 rows consist of the coefficients of the polynomials r_ab where93# 0 <= a, b <= k + d. Again, the coefficients for each r_ab are inserted in order corresponding94# To each monomial term.9596for r, (i, j) in zip(range(k^2, X_dim), prods_kd):97r_ab = x^i * y^j * n98coeffs, mons = zip(*list(r_ab))99r_dict = {k: v for k, v in zip(mons, coeffs)}100row = vector([n * t(x=X, y=Y) if t in r_dict else 0 for t in s_terms])101M[r] = row102103# Coron describes a triangularization algorithm to triangularize M, but claims that obtaining the104# Hermite normal form of M works as well, so we do the latter since Sage already has it implemented.105M = M.hermite_form()106107# As mentioned above, `rest` contains the monomials other than the k^2 ones we chose at the beginning.108l = len(rest)109110# Performs LLL on L_2111L_2 = M[list(range(k^2, k^2 + l)), list(range(k^2, k^2 + l))].LLL()112113# The first row of the LLL-reduced L_2 contains a short vector of coefficients b_1114# corresponding to the coefficients of a polynomial h(x, y) that is not a multiple of p(x, y).115# Irreducibility of p(x, y) implies that p(x, y) and h(x, y) are algebraically independent116# and that they share a root (x_0, y_0).117118# Builds h(x, y) by summing the products of monomials and their coefficient terms, and dividing out by119# the extra factors of X^i*Y^j.120h = sum(coeff * term // (X^term.degree(x) * Y^term.degree(y)) for (coeff, term) in zip(L_2[0], rest))121122# Takes the resultant of h(x, y) and p(x, y).123q = h.resultant(p, variable=y)124125# Obtains the roots x_i of q as a univariate polynomial in x over Z if they exist. Sage implements this via .roots()126# Then finds roots for q(x_i, y) as a univariate polynomial in y over Z if they exist.127roots_x = q.univariate_polynomial().roots(multiplicities=False)128129roots_y = []130for x_0 in roots_x:131y_0 = p(x=x_0).univariate_polynomial().roots(multiplicities=False)132roots_y.append(y_0[0] if y_0 else None)133134if debug and len(roots_x) > 0 and len(roots_y) > 0:135print(f"Found roots for p: x = {roots_x}, y = {roots_y}.")136137return roots_x, roots_y138139140if __name__ == "__main__":141142# Factors N = pq given the most significant bits of p and q.143P.<x, y> = PolynomialRing(ZZ)144prime_size = 512145p = random_prime(1<<prime_size, proof=False)146q = random_prime(1<<prime_size, proof=False)147148N = p * q149150# Lower bits of p151lower = 300152bits = prime_size - lower153mask = 1 << bits154# How many MSBs we want to preserve155# p // mask * mask zeros out log_2(mask) number of LSBs of p156157p_0 = (p // mask) * mask158q_0 = (N // p_0 // mask) * mask159160X, Y = mask << 1, mask << 1161poly = (x + p_0) * (y + q_0) - N162163print("--Given arguments--")164print(f"Size of primes: {prime_size}")165print(f"Number of known MSBs of p: {bits}")166print(f"N = {N}")167print("\n")168res = coppersmith_bivariate(poly, X, Y, k=4)169print("\n")170if not res:171raise ValueError("No roots found.")172173x_s, y_s = res174p_r, q_r = 0, 0175176# 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.177178for x_0, y_0 in itertools.product(x_s, y_s):179p_r = p_0 + x_0180q_r = q_0 + y_0181182if p_r * q_r == N:183print(f"Successfully factored N!")184print(f"p = {p_r}")185print(f"q = {q_r}")186break187188assert p_r * q_r == N, "Recovered values were incorrect."189190191192193194