From 76cdd606e42e0ff732f53df2cec2356e20177452 Mon Sep 17 00:00:00 2001 From: maxale Date: Wed, 5 May 2021 13:32:30 -0400 Subject: [PATCH 1/2] Added support for a modulus, when it's given. Simplified some chunks of code. --- coppersmith.sage | 145 ++++++++++++++++++++++++----------------------- 1 file changed, 74 insertions(+), 71 deletions(-) diff --git a/coppersmith.sage b/coppersmith.sage index a36102a..108fe49 100644 --- a/coppersmith.sage +++ b/coppersmith.sage @@ -1,6 +1,6 @@ -def coron(pol, X, Y, k=2, debug=False): +def coron(pol, X, Y, M=None, k=2, debug=False): """ - Returns all small roots of pol. + Returns all small roots of pol over the integers (modulo M if it is given). Applies Coron's reformulation of Coppersmith's algorithm for finding small integer roots of bivariate polynomials modulo an integer. @@ -9,6 +9,7 @@ def coron(pol, X, Y, k=2, debug=False): pol: The polynomial to find small integer roots of. X: Upper limit on x. Y: Upper limit on y. + M: Modulus. If M==None, then pol is considered over the integers. k: Determines size of lattice. Increase if the algorithm fails. debug: Turn on for debug print stuff. @@ -23,101 +24,103 @@ def coron(pol, X, Y, k=2, debug=False): raise ValueError("pol is not bivariate") P. = PolynomialRing(ZZ) - pol = pol(x,y) + pol = P( pol(x,y) ) + + # removing common factor of the coefficients + if M: + M //= gcd(M,pol.content()) + pol //= pol.content() + + if len(pol.factor()) > 1: + raise ValueError("pol is reducible") # Handle case where pol(0,0) == 0 xoffset = 0 - - while pol(xoffset,0) == 0: + while pol(xoffset,0) == 0 or (M and gcd(pol(xoffset,0),M) != 1): xoffset += 1 + if debug: + print("Offset:", xoffset) pol = pol(x+xoffset,y) + p00 = pol(0,0) # Handle case where gcd(pol(0,0),X*Y) != 1 - while gcd(pol(0,0), X) != 1: + while gcd(p00, X) != 1: X = next_prime(X, proof=False) - while gcd(pol(0,0), Y) != 1: + while gcd(p00, Y) != 1: Y = next_prime(Y, proof=False) - pol = P(pol/gcd(pol.coefficients())) # seems to be helpful - p00 = pol(0,0) - delta = max(pol.degree(x),pol.degree(y)) # maximum degree of any variable + delta = max(pol.degree(x),pol.degree(y)) # maximum degree of any variable - W = max(abs(i) for i in pol(x*X,y*Y).coefficients()) - u = W + ((1-W) % abs(p00)) - N = u*(X*Y)^k # modulus for polynomials + if M: + u = M + else: + W = max(abs(i) for i in pol(x*X,y*Y).coefficients()) + u = W + ((1-W) % abs(p00)) + + N = u*(X*Y)^k # modulus for polynomials # Construct polynomials p00inv = inverse_mod(p00,N) - polq = P(sum((i*p00inv % N)*j for i,j in zip(pol.coefficients(), - pol.monomials()))) - polynomials = [] - for i in range(delta+k+1): - for j in range(delta+k+1): - if 0 <= i <= k and 0 <= j <= k: - polynomials.append(polq * x^i * y^j * X^(k-i) * Y^(k-j)) - else: - polynomials.append(x^i * y^j * N) + polq = P( sum((i*p00inv % N)*j for i,j in pol) ) + + polynomials = [ polq * x^i * y^j * X^(k-i) * Y^(k-j) if (0 <= i <= k and 0 <= j <= k) + else x^i * y^j * N for i in range(delta+k+1) for j in range(delta+k+1) ] # Make list of monomials for matrix indices - monomials = [] - for i in polynomials: - for j in i.monomials(): - if j not in monomials: - monomials.append(j) - monomials.sort() + monomials = sorted( set( sum( (i.monomials() for i in polynomials), [] ) ) ) # Construct lattice spanned by polynomials with xX and yY - L = matrix(ZZ,len(monomials)) - for i in range(len(monomials)): - for j in range(len(monomials)): - L[i,j] = polynomials[i](X*x,Y*y).monomial_coefficient(monomials[j]) + L = matrix(ZZ, len(monomials), lambda i,j: polynomials[i](X*x,Y*y).monomial_coefficient(monomials[j]) ) # makes lattice upper triangular # probably not needed, but it makes debug output pretty L = matrix(ZZ,sorted(L,reverse=True)) if debug: - print "Bitlengths of matrix elements (before reduction):" - print L.apply_map(lambda x: x.nbits()).str() + print( "Bitlengths of matrix elements (before reduction):" ) + print( L.apply_map(lambda x: x.nbits()).str() ) L = L.LLL() if debug: - print "Bitlengths of matrix elements (after reduction):" - print L.apply_map(lambda x: x.nbits()).str() + print( "Bitlengths of matrix elements (after reduction):" ) + print( L.apply_map(lambda x: x.nbits()).str() ) roots = [] for i in range(L.nrows()): if debug: - print "Trying row %d" % i + print( "Trying row %d" % i ) # i'th row converted to polynomial dividing out X and Y pol2 = P(sum(map(mul, zip(L[i],monomials)))(x/X,y/Y)) r = pol.resultant(pol2, y) - if r.is_constant(): # not independent + if r.is_constant(): # not independent continue for x0, _ in r.univariate_polynomial().roots(): - if x0-xoffset in [i[0] for i in roots]: + if x0+xoffset in [i[0] for i in roots]: continue if debug: - print "Potential x0:",x0 + print( "Potential x0:",x0 ) for y0, _ in pol(x0,y).univariate_polynomial().roots(): if debug: - print "Potential y0:",y0 - if (x0-xoffset,y0) not in roots and pol(x0,y0) == 0: - roots.append((x0-xoffset,y0)) + print( "Potential y0:",y0 ) + v = pol(x0,y0) + if M: + v %= M + if v == 0 and (x0+xoffset,y0) not in roots: + roots.append((x0+xoffset,y0)) return roots def main(): # Example 1: recover p,q prime given n=pq and the lower bits of p - print "---EXAMPLE 1---" + print( "---EXAMPLE 1---" ) nbits = 512 # bitlength of primes p = random_prime(2^nbits-1, proof=False, lbound=2^(nbits-1)) @@ -131,15 +134,15 @@ def main(): x0 = p // ln # upper bits of p y0 = q // ln # upper bits of q - print 'p =',p - print 'q =',q - print 'x0 =',x0 - print 'y0 =',y0 + print( 'p =',p ) + print( 'q =',q ) + print( 'x0 =',x0 ) + print( 'y0 =',y0 ) - print - print 'Given:' - print 'n =',n - print 'p0 =',p0 + print() + print( 'Given:' ) + print( 'n =',n ) + print( 'p0 =',p0 ) # Recovery starts here q0 = (n * inverse_mod(p0,ln)) % ln @@ -153,18 +156,18 @@ def main(): p_2 = x0_2*ln + p0 q_2 = y0_2*ln + q0 - print - print 'Recovered:' - print 'x0 =',x0_2 - print 'y0 =',y0_2 - print 'p =',p_2 - print 'q =',q_2 + print() + print( 'Recovered:' ) + print( 'x0 =',x0_2 ) + print( 'y0 =',y0_2 ) + print( 'p =',p_2 ) + print( 'q =',q_2 ) # Example 2: recover p,q prime given n=pq and the upper bits of p # This can be done with a univariate polynomial and Howgrave-Graham, # but this is another way to do it with a bivariate polynomial. - print "---EXAMPLE 2---" + print( "---EXAMPLE 2---" ) nbits = 512 # bitlength of primes p = random_prime(2^nbits-1, proof=False, lbound=2^(nbits-1)) @@ -178,13 +181,13 @@ def main(): x0 = p % ln # lower bits of p y0 = q % ln # lower bits of q - print 'p =',p - print 'q =',q + print( 'p =',p ) + print( 'q =',q ) - print - print 'Given:' - print 'n =',n - print 'p0 =',p0 + print() + print( 'Given:' ) + print( 'n =',n ) + print( 'p0 =',p0 ) # Recovery starts here q0 = floor(n / (p0*ln))//ln @@ -197,12 +200,12 @@ def main(): p_2 = p0*ln + x0_2 q_2 = q0*ln + y0_2 - print - print 'Recovered:' - print 'x0 =',x0_2 - print 'y0 =',y0_2 - print 'p =',p_2 - print 'q =',q_2 + print() + print( 'Recovered:' ) + print( 'x0 =',x0_2 ) + print( 'y0 =',y0_2 ) + print( 'p =',p_2 ) + print( 'q =',q_2 ) if __name__ == '__main__': main() From 1010056c98ef3e58df1e4d2e309607ceef76934a Mon Sep 17 00:00:00 2001 From: Max Alekseyev Date: Tue, 10 Oct 2023 10:53:08 -0400 Subject: [PATCH 2/2] Update README.md removed outdated stuff --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 41c2deb..c81a7e6 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,8 @@ Implements Coron's reformulation of Coppersmith's algorithm for finding small in Paper: http://www.jscoron.fr/publications/bivariate.pdf -Used in CSAW CTF Quals 2016 to solve Still Broken Box. (BTW, if you want an implementation of a crypto algorithm, write a crypto CTF challenge that needs it and read writeups.) - -~~**Warning: CTF Quality Code!**~~ Should be much more readable now. - -## Why? -Why not? ## Doesn't Sage provide this with `small_roots()`? -`small_roots()` only works with univariate polynomials. (which still would have saved me a lot of time in CSAW...) +`small_roots()` only works with univariate polynomials. + +## History +Used in CSAW CTF Quals 2016 to solve Still Broken Box. (BTW, if you want an implementation of a crypto algorithm, write a crypto CTF challenge that needs it and read writeups.)