From cbbc803615f3e5f0990405b186e71093c9c7b8ee Mon Sep 17 00:00:00 2001 From: username1565 <39200328+username1565@users.noreply.github.com> Date: Sat, 12 Oct 2019 22:44:23 +0300 Subject: [PATCH 1/2] Python3 (3.2.5) + changes + additions 1. mini_esdsa.py. Rewrite for Python 3.2.5 Add additional functions: - fast pow_mod, to check is point on curve or not - faster. - add () for print operator. - remove space at line 202 - add pow_mod as parameter for contains function. By default this is 0, and function can be called with two parameters. - add comment for additive inversion - add functions to subtract, divide, get Y-coordinate and Point, by X-coordinate and parity. - fix mult_inv function. Now this return tuple with gcd, if this not 1. - add two optional parameters for brute-force function. start=0, and show_each=0; by default this is nulls. start=N run brutefource from specified start-number, show_each=M - show the new start number, after each M checked invalid points. - for baby-step-giant-step method, add optional parameter m=0, because this was been hardcoded as sqrt(n). See comments there. - maybe giant steps need to be fixed... - add additional cycle to restart pollard_rho, because sometimes this is not working, when mult_inv is incorrect. Now this restarting, if mult_inv return tupple, not nubmer. - add test function - run one test as demo. - add commented test in cycle. 2. Readme.md - Fixed. Maybe need to add some additional commands there. --- README.md | 25 +++--- mini_ecdsa.py | 223 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 176 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index e881c3a..4b81891 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ You can find a really nice introduction to elliptic curve cryptography on [Andre To use this module as is, instead of importing it for use in another project, let's open a Python console in the directory where it's stored and run the command below. ``` ->>> execfile('mini_ecdsa.py') +>>> exec(open("./mini_ecdsa.py").read()) ``` To begin, we need to define a nonsingular elliptic curve over a field of prime characteristic, or over the rationals. `CurveOverFp(a,b,c,p)` will define an elliptic curve from the equation y^2 = x^3 + ax^2 + bx + c over F_p. `CurveOverQ(a,b,c)` will define a curve using the same equation over the rationals. This module assumes the coefficients a, b, and c are integers. @@ -48,9 +48,9 @@ y^2 = x^3 + x + 2 over Q True >>> C.contains(Q) True ->>> print C.add(P,Q) +>>> print(C.add(P,Q)) (1,-2) ->>> print C.mult(Q,4) +>>> print(C.mult(Q,4)) Inf >>> C.generate(Q) ['Inf', '(-1,0)', '(1,2)', '(1,-2)'] @@ -86,8 +86,8 @@ Digital signatures generated by ECDSA consist of a public key Q as well as two p ``` >>> msg = 'this is an important message' ->>> sig = sign(msg, C, P, 131, key) -ECDSA sig: (Q, r, s) = ((1449,1186), 105, 8) +>>> sig = sign(msg.encode('utf-8'), C, P, 131, key) +ECDSA sig: (Q, r, s) = ((2387,711), 125, 50) ``` Another randomly selected positive integer smaller than the order of P is chosen as part of the signature generation process. This value, k, is a [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce). It is crucially important to generate a new one each time a message is signed. As a consequence, if a fixed message is signed multiple times, a different signature will be produced each time. Well-known attacks on ECDSA have exploited implementations which have problems generating k. @@ -95,9 +95,9 @@ Another randomly selected positive integer smaller than the order of P is chosen The recipient can verify the authenticity of the message by calling `verify`, which takes a message and signature, as well as the public curve parameters, and checks that the signature is valid. Any modification of the message will, with very high probability, result in verify returning `False`. ``` ->>> verify('this is an important message', C, P, 131, sig) +>>> verify('this is an important message'.encode('utf-8'), C, P, 131, sig) True ->>> verify('thiz is an important mossage', C, P, 131, sig) +>>> verify('thiz is an important mossage'.encode('utf-8'), C, P, 131, sig) False ``` @@ -106,8 +106,7 @@ False ``` >>> C = CurveOverFp(0, 0, 7, 2**256-2**32-2**9-2**8-2**7-2**6-2**4-1) y^2 = x^3 + 7 over F_115792089237316195423570985008687907853269984665640564039457584007908834671663 ->>> P = Point(55066263022277343669578718895168534326250603453777594175500187360389116729240, -... 32670510020758816978083085130507043184471273380659243275938904335757337482424) +>>> P = Point(55066263022277343669578718895168534326250603453777594175500187360389116729240,32670510020758816978083085130507043184471273380659243275938904335757337482424) >>> n = 115792089237316195423570985008687907852837564279074904382605163141518161494337 ``` @@ -122,11 +121,11 @@ Generating keypairs, signing, and authenticating are all done exactly as in the Priv key: 50815196737043990229212712040447958865064188767262580693117504952509239687366 Publ key: (60178516215593300273458789571475750050105656844208932820175446762050535381256,92933466624192676140900093650081093228918214155456856436706041935976250501492) >>> msg = 'this is an important message' ->>> sig = sign(msg, C, P, n, key) -ECDSA sig: (Q, r, s) = ((60178516215593300273458789571475750050105656844208932820175446762050535381256,92933466624192676140900093650081093228918214155456856436706041935976250501492), 67756844576696401107541526159652184771668032529513958275679795576766605561987, 18515072816757752760109761447693938223957409424050002694174985246358775622034) ->>> verify('this is an important message', C, P, n, sig) +>>> sig = sign(msg.encode('utf-8'), C, P, n, key) +ECDSA sig: (Q, r, s) = ((101009753524284455683527059639760708650603091496216267843569752755013360588206,80820713767805695085573982412000542390137294612577964946170582917066926184868), 103388573995635080359749164254216598308788835304023601477803095234286494993683, 63310347199411496995536543411223361953377643452656880364442100146757805748153) +>>> verify('this is an important message'.encode('utf-8'), C, P, n, sig) True ->>> verify('this is a faked important message', C, P, n, sig) +>>> verify('this is a faked important message'.encode('utf-8'), C, P, n, sig) False ``` diff --git a/mini_ecdsa.py b/mini_ecdsa.py index e28e9e3..77ab882 100644 --- a/mini_ecdsa.py +++ b/mini_ecdsa.py @@ -1,5 +1,6 @@ #Elliptic curve basics, tools for finding rational points, and ECDSA implementation. #Brendan Cordy, 2015 +#modified for Python 3.2.5 from fractions import Fraction from math import ceil, sqrt @@ -7,6 +8,16 @@ from hashlib import sha256 from time import clock +def pow_mod(x, y, z): #fast pow mod (need to fast check is point on the curve on not) + "Calculate (x ** y) % z efficiently." + number = 1 + while y: + if y & 1: + number = number * x % z + y >>= 1 + x = x * x % z + return number + #Affine Point (+Infinity) on an Elliptic Curve --------------------------------------------------- class Point(object): @@ -52,7 +63,7 @@ class Curve(object): def __init__(self, a, b, c, char, exp): self.a, self.b, self.c = a, b, c self.char, self.exp = char, exp - print self + print(self) def __str__(self): #Cases for 0, 1, -1, and general coefficients in the x^2 term. @@ -188,7 +199,7 @@ def get_points(self): #If the constant term and b are both zero, factor out x^2 and look for rational #roots of the resulting linear polynomial. Any such roots must divide a. elif self.a != 0: - for x in divisors(self.a): + for x in divisors(self.a): P = Point(x,y) if 0 == x*x*x+self.a*x*x+self.b*x+const_term and self.has_finite_order(P): points.append(P) @@ -272,12 +283,12 @@ def torsion_group(self): highest_order = self.order(P) #If this point generates the entire torsion group, the torsion group is cyclic. if highest_order == len(self.get_points()): - print 'Z/' + str(highest_order) + 'Z' + print('Z/' + str(highest_order) + 'Z') #If not, by Mazur's Theorem the torsion group must be a direct product of Z/2Z #with the cyclic group generated by the highest order point. else: - print 'Z/2Z x ' + 'Z/' + str(highest_order) + 'Z' - print C.show_points() + print('Z/2Z x ' + 'Z/' + str(highest_order) + 'Z') + print(C.show_points()) #Elliptic Curves over Prime Order Fields --------------------------------------------------------- @@ -291,11 +302,13 @@ def __init__(self, a, b, c, p): def secp256k1(cls): return cls(0, 0, 7, 2**256-2**32-2**9-2**8-2**7-2**6-2**4-1) - def contains(self, P): + def contains(self, P, pow_mod=0): if P.is_infinite(): return True - else: - return (P.y*P.y) % self.char == (P.x*P.x*P.x + self.a*P.x*P.x + self.b*P.x + self.c) % self.char + elif(pow_mod==0): # check: (y^2) mod p == (x^3 + ax^2 + bx + c) mod p + return (P.y*P.y) % self.char == (P.x*P.x*P.x + self.a*P.x*P.x + self.b*P.x + self.c) % self.char # true/false + else: # check this faster, using pow_mod + return (pow_mod(P.y, 2, self.char) == (pow_mod(P.x, 3, self.char) + pow_mod(self.a*P.x, 2, self.char) + (self.b * P.x) + self.c)%self.char); #true/false def get_points(self): #Start with the point at infinity. @@ -309,11 +322,11 @@ def get_points(self): points.append(P) return points - def invert(self, P): + def invert(self, P): #additive inversion if P.is_infinite(): return P else: - return Point(P.x, -P.y % self.char) + return Point(P.x, -P.y % self.char) #-Q = (x_Q, -y_Q%p) def add(self, P_1, P_2): #Adding points over Fp and can be done in exactly the same way as adding over Q, @@ -338,6 +351,26 @@ def add(self, P_1, P_2): y = (-ld*x - nu) % self.char return Point(x,y) + def subtract(self, point, another): #C.subtract(from, minus) + if(another.is_infinite()): + return point; + else: + return self.add(point, self.invert(another)); # (P - Q) = P + (-Q) + + def divide_point(self, point, k, n): + print("k", k, "n", n, "mult_inv(k, n)", mult_inv(k, n)); + return self.mult(point, mult_inv(k, n)) # (P / k) = P * (k^-1 mod n) + + def getY(self, X, Y_parity_bit=0): # for each X there is two Y on curve, odd and even. + a = (pow_mod(X, 3, self.char) + 7) % self.char # a = ( ( ( (x^3) mod n ) ax^2 + bx + c ) mod n ) + Y = pow_mod(a, (self.char+1)//4, self.char) # y = a^{(n+1)//4} mod n + if Y % 2 != Y_parity_bit: + Y = -Y % self.char # invert Y + return Y; + + def get_point_by_X(self, X, Y_parity_bit=0): # each point can be encoded as X and parity_bit for one from two Y's. + return Point(X, self.getY(X, Y_parity_bit)); #return this. + #Elliptic Curves over Prime Power Order Fields --------------------------------------------------- class CurveOverFq(Curve): @@ -371,12 +404,14 @@ def euclid(sml, big): #gcd = y*(big - (big//sml)*sml) + x*sml = (x - (big//sml)*y)*sml + y*big. return (g, x - (big//sml)*y, y) -#Compute the multiplicative inverse mod n of a with 0 < a < n. -def mult_inv(a, n): +# Compute the multiplicative modular inverse mod n of a with 0 < a < n. +def mult_inv(a, n): # return b, for which: a * b === 1 (mod n), or return tuple with gcd g, x, y = euclid(a, n) #If gcd(a,n) is not one, then a has no multiplicative inverse. if g != 1: - raise ValueError('multiplicative inverse does not exist') + #raise ValueError('multiplicative inverse does not exist for value a mod n =', a, "mod", n); + #return str('multiplicative inverse does not exist for value a mod n = ')+str(a)+str(" mod ")+str(n); + return ("gcd!=1", g); # if value have no modular inverse - don't show error and just return tuple, with gcd. #If gcd(a,n) = 1, and gcd(a,n) = x*a + y*n, x is the multiplicative inverse of a. else: return x % n @@ -400,8 +435,8 @@ def generate_keypair(curve, P, n): sysrand = SystemRandom() d = sysrand.randrange(1, n) Q = curve.mult(P, d) - print "Priv key: d = " + str(d) - print "Publ key: Q = " + str(Q) + print("Priv key: d = " + str(d)) + print("Publ key: Q = " + str(Q)) return (d, Q) #Create a digital signature for the string message using a given curve with a distinguished @@ -417,7 +452,7 @@ def sign(message, curve, P, n, keypair): R = curve.mult(P, k) r = R.x % n s = (mult_inv(k, n) * (z + r*d)) % n - print 'ECDSA sig: (Q, r, s) = (' + str(Q) + ', ' + str(r) + ', ' + str(s) + ')' + print('ECDSA sig: (Q, r, s) = (' + str(Q) + ', ' + str(r) + ', ' + str(s) + ')') return (Q, r, s) #Verify the string message is authentic, given an ECDSA signature generated using a curve with @@ -445,19 +480,22 @@ def verify(message, curve, P, n, sig): #Key Cracking Functions -------------------------------------------------------------------------- #Find d for which Q = dP by simply trying all possibilities -def crack_brute_force(curve, P, n, Q): +def crack_brute_force(curve, P, n, Q, start=0, show_each=0): #it can be parallelized start_time = clock() - for d in range(n): + print("start: ", start, "up to:", n); + for d in range(start, n): + if(show_each!=0 and (d%show_each==0)): print("crack_brute_force: last incorrect d = ", d); #this number can be used as "start" value to continue. if curve.mult(P,d) == Q: end_time = clock() - print "Priv key: d = " + str(d) - print "Time: " + str(round(end_time - start_time, 3)) + " secs" + print("Priv key: d = " + str(d)) + print("Time: " + str(round(end_time - start_time, 3)) + " secs") break #Find d for which Q = dP using the baby-step giant-step algortihm. -def crack_baby_giant(curve, P, n, Q): +def crack_baby_giant(curve, P, n, Q, m=0): #m can be specified, 0 by default sqrt(n) start_time = clock() - m = int(ceil(sqrt(n))) + if m==0: m = int(ceil(sqrt(n))); #for m = sqrt(n), Baby-Step-Giant-Step working for O(sqrt(n)) multiplies, and eating sqrt(n) memory. + #else - O(n/m) multiplies, and eating m memory for storing all points for baby-steps. #Build a hash table with all bP with 0 < b < m using a dictionary. The dicitonary value #stores b so that it can be quickly recovered after a matching giant step hash. baby_table = {} @@ -466,51 +504,67 @@ def crack_baby_giant(curve, P, n, Q): baby_table[str(bP)] = b #Check if Q - gmP is in the hash table for all 0 < g < m. If we get such a matching hash, #we have Q - gmP = bP, so extract b from the dictionary, then Q = (b + gm)P. - for g in range(m): + for g in range((int(n/m)+1)): R = curve.add(Q, curve.invert(curve.mult(P, g*m))) if str(R) in baby_table.keys(): b = baby_table[str(R)] end_time = clock() - print "Priv key: d = " + str((b + g*m) % n) - print "Time: " + str(round(end_time - start_time, 3)) + " secs" + print("Priv key: d = " + str((b + g*m) % n)) + print("Time: " + str(round(end_time - start_time, 3)) + " secs") break #Find d for which Q = dP using Pollard's rho algorithm. def crack_rho(curve, P, n, Q, bits): start_time = clock() - R_list = [] - #Compute 2^bits randomly selected linear combinations of P and Q, storing them as triples - #of the form (aP + bQ, a, b) in R_list. - for i in range(2**bits): - a, b = randrange(0,n), randrange(0,n) - R_list.append((curve.add(curve.mult(P,a), curve.mult(Q,b)), a, b)) - #Compute a new random linear combination of P and Q to start the cycle-finding. - aT, bT = randrange(0,n), randrange(0,n) - aH, bH = aT, bT - T = curve.add(curve.mult(P,aT), curve.mult(Q,bT)) - H = curve.add(curve.mult(P,aH), curve.mult(Q,bH)) while True: - #Advance the tortoise one step, by adding a point in R_list determined by the last b - #bits in the binary explansion of the x coordinate of the current position. - j = int(bin(T.x)[len(bin(T.x)) - bits : len(bin(T.x))], 2) - T, aT, bT = curve.add(T, R_list[j][0]), (aT + R_list[j][1]) % n, (bT + R_list[j][2]) % n - #Advance the hare two steps, again by adding points in R_list determined by the last - #b bits in the binary explansion of the x coordinate of the current position. - for i in range(2): - j = int(bin(H.x)[len(bin(H.x)) - bits : len(bin(H.x))], 2) - H, aH, bH = curve.add(H, R_list[j][0]), (aH + R_list[j][1]) % n, (bH + R_list[j][2]) % n - #If the tortoise and hare arrive at the same point, a cycle has been found. - if(T == H): - break - #It is possible that the tortoise and hare arrive at exactly the same linear combination. - if bH == bT: - end_time = clock() - print "Rho failed with identical linear combinations" - print str(end_time - start_time) + " secs" - else: - end_time = clock() - print "Priv key: d = " + str((aT - aH) * mult_inv((bH - bT) % n, n) % n) - print "Time: " + str(round(end_time - start_time, 3)) + " secs" + R_list = [] + #Compute 2^bits randomly selected linear combinations of P and Q, storing them as triples + #of the form (aP + bQ, a, b) in R_list. + for i in range(2**bits): + a, b = randrange(0,n), randrange(0,n) + R_list.append((curve.add(curve.mult(P,a), curve.mult(Q,b)), a, b)) + #Compute a new random linear combination of P and Q to start the cycle-finding. + aT, bT = randrange(0,n), randrange(0,n) + aH, bH = aT, bT + T = curve.add(curve.mult(P,aT), curve.mult(Q,bT)) + H = curve.add(curve.mult(P,aH), curve.mult(Q,bH)) + while True: + #Advance the tortoise one step, by adding a point in R_list determined by the last b + #bits in the binary explansion of the x coordinate of the current position. + j = int(bin(T.x)[len(bin(T.x)) - bits : len(bin(T.x))], 2) + T, aT, bT = curve.add(T, R_list[j][0]), (aT + R_list[j][1]) % n, (bT + R_list[j][2]) % n + #Advance the hare two steps, again by adding points in R_list determined by the last + #b bits in the binary explansion of the x coordinate of the current position. + for i in range(2): + j = int(bin(H.x)[len(bin(H.x)) - bits : len(bin(H.x))], 2) + H, aH, bH = curve.add(H, R_list[j][0]), (aH + R_list[j][1]) % n, (bH + R_list[j][2]) % n + #If the tortoise and hare arrive at the same point, a cycle has been found. + if(T == H): + break + #It is possible that the tortoise and hare arrive at exactly the same linear combination. + if bH == bT: + end_time = clock() + print("Rho failed with identical linear combinations") + print(str(end_time - start_time) + " secs") + else: + end_time = clock() + print("\nPollard-rho, results:"); + modular_inverse = mult_inv((bH - bT) % n, n); #Some values have no modular inverse, and will be returned gcd. + + if isinstance(modular_inverse, int): + modular_inverse = modular_inverse % n + priv = ((aT - aH) * modular_inverse) % n; + pub = curve.mult(P, priv); + if(pub.__eq__(Q)): + print("Priv key: d = " + str(priv) + str(", pub: ")+str(pub)); + print("Time: " + str(round(end_time - start_time, 3)) + " secs") + break; + else: + print("pub not equal Q", "pub", pub, "Q", Q); + continue; + elif ( (isinstance(modular_inverse, tuple)) and (modular_inverse[0] == "gcd!=1") ) : + print("invalid modinv... gcd = ", modular_inverse[1], "Try again..."); + continue; #Find d by exploiting two messages signed with the same value of k. def crack_from_ECDSA_repeat_k(curve, P, n, m1, sig1, m2, sig2): @@ -520,10 +574,61 @@ def crack_from_ECDSA_repeat_k(curve, P, n, m1, sig1, m2, sig2): #and m2 were signed with distinct k, in which case the value of d computed below will be #wrong, but this is a very unlikely scenario. if not r1 == r2: - print "Messages signed with distinct k" + print("Messages signed with distinct k") else: z1 = hash_and_truncate(m1, n) z2 = hash_and_truncate(m2, n) k = (z1 - z2) * mult_inv((s1 - s2) % n, n) % n d = mult_inv(r1, n) * ((s1 * k) % n - z1) % n - print "Priv key: d = " + str(d) + print("Priv key: d = " + str(d)) + +#tests: +def run_test(): + print("_____________________") + #smaller elliptic curve + C = CurveOverFp(0, 0, 7, 211); + P = Point(150, 22); + print("\n\n", "smaller curve:", C, ", generator point: ", P); + + n = C.order(P) + print("n", n); + + (d, Q) = generate_keypair(C, P, n); #limited value of d (max = n) + print("d", d, "Q", Q); + + print("\ntest brute_force:"); + crack_brute_force(C, P, n, Q, 150, 100); + + print("\ntest BSGS:"); + crack_baby_giant(C, P, n, Q); + + print("\ntest pollard_rho:"); + crack_rho(C, P, n, Q, 1); + + (d1, Q1) = generate_keypair(C, P, n); + print("d1", d1, "Q1", Q1); + + print("\ntest subtraction:", Q.__eq__(C.add(C.subtract(Q, Q1),Q1))); + print("\ntest divide:", Q.__eq__(C.mult(C.divide_point(Q, d1, n), d1))); + + print("test get_Y (even Y):", C.getY(Q1.x, 0)); + print("test get_point_by_X (odd Y):", C.get_point_by_X(Q1.x, 1)); + print("test is on curve?:", C.contains(Point(Q1.x, C.getY(Q1.x,0))), C.contains(C.get_point_by_X(Q1.x,1))); + print("test is on curve (pow_mod)?:", C.contains(Point(Q1.x, C.getY(Q1.x,0))), C.contains(C.get_point_by_X(Q1.x,1))); + print("_____________________") +#end function + +run_test(); #result of one test just as demo + +""" +# test many random points with many iterations. +import time #to using interval +interval = 0.5; #seconds +# run tests +print ("Start : %s" % time.ctime()) +for i in range(0, 100): + print ("continue : %s" % time.ctime()) + time.sleep( interval ) # wait... + run_test(); # repeat test again, after some time +print ("End : %s" % time.ctime()) +""" \ No newline at end of file From 54d2ba23973819806e85456941c3c1a099434bc7 Mon Sep 17 00:00:00 2001 From: username1565 <39200328+username1565@users.noreply.github.com> Date: Tue, 29 Oct 2019 03:27:38 +0200 Subject: [PATCH 2/2] Add Elliptic-Curve-Cryptography implementation Changes: mini_ecdsa.py - comment test print in divide_point function. return Y%self.char in getY function return X%self.char in get_point_by_X, because for big x was been returned the point with big x, and this contains on curve, because modulo in [0,...,p] is correct. Add two strings with comments and remove all tests. Tests are moved in tests_mini_ecdsa.py tests_mini_ecdsa.py - add moved tests from old mini_ecdsa.py ECC.py - add draft Elliptic-Curve-Encryption implementation. Need to test this, fix bug and optimize. tests_ECC.py - add tests the functions in ECC.py in this file. See source code, and read the comments, try to optimize. Unlicense and WTFPL AS IS. --- ECC.py | 325 ++++++++++++++++++++++++++++++++++++++++++++ mini_ecdsa.py | 60 +------- tests_ECC.py | 167 +++++++++++++++++++++++ tests_mini_ecdsa.py | 53 ++++++++ 4 files changed, 552 insertions(+), 53 deletions(-) create mode 100644 ECC.py create mode 100644 tests_ECC.py create mode 100644 tests_mini_ecdsa.py diff --git a/ECC.py b/ECC.py new file mode 100644 index 0000000..7d88c77 --- /dev/null +++ b/ECC.py @@ -0,0 +1,325 @@ +#mini_ecdsa.py must to be in the same folder with this file. +from mini_ecdsa import *; #this script have dependency from mini_ecdsa.py, and can working with functions, which contains there. + +''' + Elliptic-Curve Cryptography (ECC) implementation - draft version. + +To make encryption and decryption messages, on the elliptic curve in finite field, +need to encode and decode this data, as a points, which are contains on this elliptic-curve. + +If EC, like secp256k1, have the "n" unique points, and if "n" - this is a "prime number", +then in the "semigroup" of "abel-group on this EC", there is "(n-1)/2" unique points with "unique coodinates". +In the "second semigroup" of this EC, there is a points, which are "inverted" of the points in the first semigroup. +This "inverted points" have the "same X-coodinates", but "parity of Y-coorinate" is inverted. + + +""" +# See the following example code: +# Generate all points for small curve +C = CurveOverFp(0, 0, 7, 211); #(y^2) mod p = (x^3 + 7) mod p +P = Point(150, 22); #generator point, which "contains" on the curve. +n = C.order(P) #points on the curve. +print("\n\nSmall curve: "+str(C), "\nP: Point"+str(P), "\npoints: n = "+str(n)); + +for i in range(0, n): + point = C.mult(P, i); + print(str(i)+" * P = "+str(point)+"\t\tis on curve: "+str(C.contains(point)),"; y_parity:", (point.y%2)); +""" + +quote: + ... + 95 * P = (180,38) is on curve: True ; y_parity: 0 + 96 * P = (187,113) is on curve: True ; y_parity: 1 + 97 * P = (22,59) is on curve: True ; y_parity: 1 + 98 * P = (126,37) is on curve: True ; y_parity: 1 + 99 * P = (31,141) is on curve: True ; y_parity: 1 + 100 * P = (31,70) is on curve: True ; y_parity: 0 + 101 * P = (126,174) is on curve: True ; y_parity: 0 + 102 * P = (22,152) is on curve: True ; y_parity: 0 + 103 * P = (187,98) is on curve: True ; y_parity: 0 + 104 * P = (180,173) is on curve: True ; y_parity: 1 + ... + +As you can see, points (99*P and 100*P, 98*P and 101*P, ..., etc...) +have the same "X-coordinates", but different "Y-coordinates", and the "parity of Y" for this points are inverted. +That means, in one semigroup, there is maximum (n-1)/2 unique "X-coordinates", with values from 0 up to p (211). +Also, that means, each point from (0 up to n) "can be encoded" as "one number": (x*2 + y_parity_bit). + +But, not all nubmers from [0-p] can be encoded as one point, which contains on elliptic curve. +And... Each number from 0-p can be writted as: x = k*c + r, +where c - qotient, k - divisor (2 by default), r - remainder (from 0 to k-1. Minumum is two variants if k = 2: 0 and 1); +That means, each number from 0 to p, can be divided to 2, +and c (which is lesser than p/2) can be encoded as point with x-coordinate from 0 to n, if this point contains on curve. +Else, possible to continue divide the number, with add sequence of remainders (one from excluded points - 0 or 1). + +In this case, each number can be encoded as sequence of the points on elliptic cutve in finite field. +Decoding is the reversive operation, because all points are contains on the curve, and have specified X and Y coordinates. + +After encoding the message on elliptic curve, result array with points can be encrypted, +by multiply all points to secret scalar contant. + k*P = Q; +Reversive operation for decripting the points, is division the result-points for this scalar constant: + Q/k = Q * k^(-1) mod p = Q * mult_inv(k, n); + +Meaning this all, each message can be splitted by blocks with values from 0 up to p, +because the coordinates for each point have values from 0 to p. + +The following code is implementation of ECC encryption and decryption (text and numbers), using small curve and secp256k1 (bitcoin elliptic-curve). + +Summarry: +____________________________________________________________________________ +pre-defined: elliptic curve parameters; array with excluded points; key; +ciphertext: array with numbers or ciphertext string +message: number or text_string +encryption: message -> array with points * key -> array with numbers -> ciphertext string = ciphertext; +decryption: ciphertext -> array with numbers -> array with points / key -> decode array with points = message. +____________________________________________________________________________ +To exchange the key, can be used Elliptic-Curve Diffie-Hellman (ECDH): https://github.com/username1565/ecdh +See working demo - here: https://username1565.github.io/ECDH/ +''' + +""" +#example code to encode text to hex and decode hex to text. + +import binascii; +m_hex = binascii.hexlify(m.encode("utf-8")); +print("m_hex: ", m_hex); #hex + +decrypted_message = binascii.unhexlify(m_hex).decode('utf8') +print("decrypted_message: ", decrypted_message); #text +""" + +import random; #need to generate points and numbers, using random.randrange() + +#Generate array with unique excluded points. +#This points need to encode all numbers from 0 up to p, as points, which contains on elliptic-curve. +def generate_excluded_points(C, N='random'): + N = random.randrange(3, C.char) if (N=='random' or (not(isinstance(N, int)))) else N; + + if(N<3): + print("Mimumum 3 points needed."); + N = 3; + + array = []; + + in_array = 0; + while(len(array)10 and len(array)>3): #not lesser than 3 points must be generated 2 minimum + 1 as marker. + print("Too many points already exists in array. Return array."); + break; + random_x = random.randrange(0, C.char); + random_y_parity = random.randrange(0, 2); + point = C.get_point_by_X(random_x, random_y_parity); + is_on_curve = C.contains(point); + if(is_on_curve and (not(point in array)) and (point.inf==False)): #points on curve, whithout duplicates, and O + array.append(point); + elif(point.inf==True): #if O - remove it. + array.pop(0); + elif(not(point in array)): #if already exists + in_array+=1; +# print("point", point, " already exists in array. Skip."); + continue; + elif(is_on_curve==False): #if not contains. +# print("point", point, " not contains on curve. Skip."); + continue; + + #show array in console. + print("\nexcluded_points_array contains", len(array), "points."); + + print("\nexcluded_points_array = [\n", end = ''); + for i in range(0, len(array)): + is_on_curve = C.contains(array[i]); + print("\tPoint"+str(array[i])+(", " if i!=len(array)-1 else "")+"\n", end = ''); + print("];\n\n"); + + #input("Array with points was been generated. Press Enter to continue...") #pause and continue after press any key... + return array; + +# By default key = 1, and points just will be encoded. +# Else, if key!==0 and 0=", C.char, "break;"); + return []; + else: + temp_point = C.get_point_by_X(x//2, x%2); + encrypted_temp_point = C.mult(temp_point, key); + is_on_curve = C.contains(encrypted_temp_point); + temp_point_was_found_in_excluded_points = False; + for item in range(0, len(excluded_points_array)): + if( ( temp_point.x == excluded_points_array[item].x ) and ( temp_point.y == excluded_points_array[item].y )): + temp_point_was_found_in_excluded_points = True; + break; + else: + continue; + if((is_on_curve) and (temp_point_was_found_in_excluded_points == False) and (encrypted_temp_point.inf==False)): + encoded.append( int(int(encrypted_temp_point.x*2)+int(encrypted_temp_point.y%2)) ); + else: + remainder_index = int(x % (len(excluded_points_array)-1)); + excluded_point = excluded_points_array[remainder_index]; + excluded_point = C.mult(excluded_point, key); + excluded_point_as_int = int(int(excluded_point.x*2)+int(excluded_point.y%2)); + encoded.append(excluded_point_as_int); + new_x_coordinate = int((x-remainder_index)//(len(excluded_points_array)-1)); + if(new_x_coordinate == 0): + encoded.append(0); + else: + new_sequence = encode_or_and_encrypt(C, new_x_coordinate, excluded_points_array, key); + for i in range(0, len(new_sequence)): + encoded.append(new_sequence[i]); + return encoded; + + +import math; #need for math.pow() +# By default key = 1, and points just will be encoded. +# Else, if key!==0 and 0 to string"; +string = array_to_string(array); +print("string: ", string); + +#back "string -> to array" ??? +array2 = array_from_string(string); +print("array2", array2); +print("array==array2", array==array2); +""" +#____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ + + +#import random #already imported. +import string + +def randomString(stringLength=10): #generate random string with specified length + """Generate a random string of fixed length """ + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(stringLength)) + +""" +#usage: +print ("Random String is ", randomString() ) +print ("Random String is ", randomString(10) ) +print ("Random String is ", randomString(10) ) +""" + +# Run tests_mini_ecdsa.py to test the functions in included mini_ecdsa.py +# Run tests_ECC.py to test Elliptic-Curve Encryption functions from this ECC.py diff --git a/mini_ecdsa.py b/mini_ecdsa.py index 77ab882..ff63da5 100644 --- a/mini_ecdsa.py +++ b/mini_ecdsa.py @@ -358,7 +358,7 @@ def subtract(self, point, another): #C.subtract(from, minus) return self.add(point, self.invert(another)); # (P - Q) = P + (-Q) def divide_point(self, point, k, n): - print("k", k, "n", n, "mult_inv(k, n)", mult_inv(k, n)); +# print("k", k, "n", n, "mult_inv(k, n)", mult_inv(k, n)); return self.mult(point, mult_inv(k, n)) # (P / k) = P * (k^-1 mod n) def getY(self, X, Y_parity_bit=0): # for each X there is two Y on curve, odd and even. @@ -366,10 +366,12 @@ def getY(self, X, Y_parity_bit=0): # for each X there is two Y on curve, od Y = pow_mod(a, (self.char+1)//4, self.char) # y = a^{(n+1)//4} mod n if Y % 2 != Y_parity_bit: Y = -Y % self.char # invert Y - return Y; +# return Y; + return Y%self.char; def get_point_by_X(self, X, Y_parity_bit=0): # each point can be encoded as X and parity_bit for one from two Y's. - return Point(X, self.getY(X, Y_parity_bit)); #return this. +# return Point(X, self.getY(X, Y_parity_bit)); #return this. + return Point(X%self.char, self.getY(X, Y_parity_bit)); #return this. #Elliptic Curves over Prime Power Order Fields --------------------------------------------------- @@ -582,53 +584,5 @@ def crack_from_ECDSA_repeat_k(curve, P, n, m1, sig1, m2, sig2): d = mult_inv(r1, n) * ((s1 * k) % n - z1) % n print("Priv key: d = " + str(d)) -#tests: -def run_test(): - print("_____________________") - #smaller elliptic curve - C = CurveOverFp(0, 0, 7, 211); - P = Point(150, 22); - print("\n\n", "smaller curve:", C, ", generator point: ", P); - - n = C.order(P) - print("n", n); - - (d, Q) = generate_keypair(C, P, n); #limited value of d (max = n) - print("d", d, "Q", Q); - - print("\ntest brute_force:"); - crack_brute_force(C, P, n, Q, 150, 100); - - print("\ntest BSGS:"); - crack_baby_giant(C, P, n, Q); - - print("\ntest pollard_rho:"); - crack_rho(C, P, n, Q, 1); - - (d1, Q1) = generate_keypair(C, P, n); - print("d1", d1, "Q1", Q1); - - print("\ntest subtraction:", Q.__eq__(C.add(C.subtract(Q, Q1),Q1))); - print("\ntest divide:", Q.__eq__(C.mult(C.divide_point(Q, d1, n), d1))); - - print("test get_Y (even Y):", C.getY(Q1.x, 0)); - print("test get_point_by_X (odd Y):", C.get_point_by_X(Q1.x, 1)); - print("test is on curve?:", C.contains(Point(Q1.x, C.getY(Q1.x,0))), C.contains(C.get_point_by_X(Q1.x,1))); - print("test is on curve (pow_mod)?:", C.contains(Point(Q1.x, C.getY(Q1.x,0))), C.contains(C.get_point_by_X(Q1.x,1))); - print("_____________________") -#end function - -run_test(); #result of one test just as demo - -""" -# test many random points with many iterations. -import time #to using interval -interval = 0.5; #seconds -# run tests -print ("Start : %s" % time.ctime()) -for i in range(0, 100): - print ("continue : %s" % time.ctime()) - time.sleep( interval ) # wait... - run_test(); # repeat test again, after some time -print ("End : %s" % time.ctime()) -""" \ No newline at end of file +# Run tests_mini_ecdsa.py to test the functions in this mini_ecdsa.py +# Run tests_ECC.py to test Elliptic-Curve Encryption functions from ECC.py \ No newline at end of file diff --git a/tests_ECC.py b/tests_ECC.py new file mode 100644 index 0000000..e00f29a --- /dev/null +++ b/tests_ECC.py @@ -0,0 +1,167 @@ +#mini_ecdsa.py and ECC.py must to be in the same folder with this file. +from ECC import *; #import function from ECC.py. All functions importing there too from mini_ecdsa.py. + +#______________________________________________________________________________________________________________________________ +# tests Elliptic-curve encryption-decryption +# initialization tests. + +print("_________________________________"); +# initialization of elliptic curve + +''' +#smaller elliptic curve +C = CurveOverFp(0, 0, 7, 211); +P = Point(150, 22); +print("\n\n"+"smaller curve:", C, ", generator point: ", P); +n = C.order(P) +print("n", n); +#(d, Q) = generate_keypair(C, P, n); #limited value of d (max = n) +#print("d", d, "Q", Q); +''' + +""" +larger elliptic curve +C = CurveOverFp(0, 1, 7, 729787) +P = Point(1,3) +n = 730819; +""" + + +#secp256k1 - this is a big elliptic curve, which is using bitcoin. +C = CurveOverFp.secp256k1(); +P = Point.secp256k1(); +n = 115792089237316195423570985008687907852837564279074904382605163141518161494337; +print("P = Point", P) +print("n =", n); +print("_________________________________"); + +#______________________________________________________________________________________________________________________________ + +# initialization of encryption-decryption key + +#import random #already imported. + +# set key for encryption. This is scalar value - k. cyphertext: Q = kP; text: P = Q/k = Q * k^(-1) mod p = Q * mult_inv(k, n); +#key = 1; #no key, just encode and decode. This can be not specifiec and key=1 by default. +#key = 2; #test with some key. +key = random.randrange(1, C.char-1); # generate this key, not 0, because all points will be nulled then. Also not C.char (because C.char%C.char == 0) +print("\nGenerated key for encryption-decryption: ", key); # show key. + +''' +print("_________________________________"); +# change limit for recursion, if you see an error. +import sys; +print(sys.getrecursionlimit()) #default 1000, and this limit can be is reached. +sys.setrecursionlimit(5000) #set this limit higher +print(sys.getrecursionlimit()) #default 1000, and this limit can be is reached. +''' + +print("_________________________________"); + + +# generate array with excluded points + +#Minumum 3 points must to be excluded. +#Two for encoding-decoding numbers, that is not a coordinates of the points, which are contains on elliptic curve, +#and one more point, as block-marker, to do descrimination the beginning of new encoded point. +#if array contains k+1 points, then: +#x = c*k + r, where x - encoded number, c - quotient, k - divisor, r - remainder point, contains in this array by index from 0 to k-1. + +excluded_points_array = generate_excluded_points(C, 3); #3 points x = c*2 + r +#excluded_points_array = generate_excluded_points(C, 4); #4 points x = c*3 + r +#excluded_points_array = generate_excluded_points(C, 8); #8 points x = c*7 + r +#excluded_points_array = generate_excluded_points(C, 'random'); #from 3 up to p-1 (C.char) + + +print("_________________________________"); + +#______________________________________________________________________________________________________________________________ + +# test encryption-decryption text + +print("\n\ntest string encryption-decryption:\n"); +#open_message = "test_text"; #utf-8 encoded string +open_message = randomString(50); #random string +#open_message = randomString(5); #shorter random string + +#print("open_message =", open_message); #show message +c = text_encode(open_message, C, excluded_points_array, key); #encoded message - array with numbers of encoded points on curve. +#print("c =", c); #show cypher-array + +# "array -> to string"; +c_string = array_to_string(c, C, excluded_points_array, key, "point_", "hex_"); #show encoded string +print("cyphertext: c_string =", c_string); #show cyphertext + +#back "string -> to array" ??? +c_array = array_from_string(c_string, C, excluded_points_array, key, "point_", "hex_"); #decode array from cyphertext +print("c == c_array", (c == c_array)); #show result of comparison arrays. + +decrypted_message = text_decode(c_array, C, excluded_points_array, n, key); #decoded message from array - text of message +#decrypted_message = text_decode(c, C, excluded_points_array, n, key); #decoded message from encoded c_array - text of message +print("open_message =", open_message, "\ndecrypted_message =", decrypted_message, "\ndecrypted_message == open_message??", decrypted_message==open_message); #compare open_message and decrypted_message + + + +print("_________________________________"); + +#______________________________________________________________________________________________________________________________ + +print("\n\ntest number encryption-decryption:\n"); +#import random; #already imported. + +#open_message = random.randrange(0, C.char); #up to p-parameter of elliptic curve. +open_message = random.randrange(0, math.pow(2,24)); #shorter number. +#open_message = random.randrange(0, int(math.pow(2,512))); #message - number. Working. + +#print("open_message =", open_message); #show number +c = int_encode(open_message, C, excluded_points_array, key); #get encrypted array - nubmers of encoded points on the elliptic curve. +#print("c =", c); #show + +# "array -> to string"; +c_string = array_to_string(c, C, excluded_points_array, key); #encode this array to string (optional parameters are default) +print("cyphertext: c_string =", c_string); #show cyphertext string + +#back "string -> to array" ??? +c_array = array_from_string(c_string, C, excluded_points_array, key, "h", "p"); #restore array from cyphertext string. +print("c == c_array", (c == c_array)); #show result of comparison arrays. + +decrypted_message = int_decode(c_array, C, excluded_points_array, n, key); #decoded message from c_array - number +#decrypted_message = int_decode(c, C, excluded_points_array, n, key); #decoded message from c - number +print( + "open_message =", open_message, + "\ndecrypted_message =", decrypted_message, + "\ndecrypted_message == open_message??", decrypted_message==open_message #compare m and decrypted_message +); + +print("_________________________________"); + + + +input("Press Enter to continue, and start 100 tests...") #pause and continue after press any key... +#run test in cycle +import time #to using interval +print ("Start : %s" % time.ctime()) +interval = 0.1; #seconds +for m in range(0, 100): + print ("continue : %s" % time.ctime()) + time.sleep( interval ) # wait... + #print("\n\n next test. Key:", key); + #open_message = random.randrange(0, C.char); + open_message = m; #use i as message. + c = int_encode(open_message, C, excluded_points_array, key); #encoded and encrypted message - array with numbers of encoded points on curve + decrypted_message = int_decode(c, C, excluded_points_array, n, key); #decoded and decrypted message - i + if(decrypted_message!=open_message): + print("\nError found! break;") + print("open_message", open_message, "!=", "decrypted_message", decrypted_message, "open_message =", open_message, "decrypted_message =", decrypted_message, "key", key); + + print("len(excluded_points_array)", len(excluded_points_array)); + for i in range(0, len(excluded_points_array)): + is_on_curve = C.contains(excluded_points_array[i]); + print("i = ", i, "excluded_points_array[i] =", excluded_points_array[i], "is_on_curve: ", is_on_curve); + break; + else: + print("m", m, "(decrypted_message==open_message)", (decrypted_message==open_message)); + + if(m%C.char==0): + excluded_points_array = generate_excluded_points(C); #regenerate array with excluded points. +print ("End : %s" % time.ctime()) diff --git a/tests_mini_ecdsa.py b/tests_mini_ecdsa.py new file mode 100644 index 0000000..3066a60 --- /dev/null +++ b/tests_mini_ecdsa.py @@ -0,0 +1,53 @@ +#mini_ecdsa.py must to be in the same folder with this file. +from mini_ecdsa import *; + +#tests the fucntions in mini_ecdsa.py +def run_test(): + print("_____________________") + #smaller elliptic curve + C = CurveOverFp(0, 0, 7, 211); + P = Point(150, 22); + print("\n\n", "smaller curve:", C, ", generator point: ", P); + + n = C.order(P) + print("n", n); + + (d, Q) = generate_keypair(C, P, n); #limited value of d (max = n) + print("d", d, "Q", Q); + + print("\ntest brute_force:"); + crack_brute_force(C, P, n, Q, 150, 100); + + print("\ntest BSGS:"); + crack_baby_giant(C, P, n, Q); + + print("\ntest pollard_rho:"); + crack_rho(C, P, n, Q, 1); + + (d1, Q1) = generate_keypair(C, P, n); + print("d1", d1, "Q1", Q1); + + print("\ntest subtraction:", Q.__eq__(C.add(C.subtract(Q, Q1),Q1))); + print("\ntest divide:", Q.__eq__(C.mult(C.divide_point(Q, d1, n), d1))); + + print("test get_Y (even Y):", C.getY(Q1.x, 0)); + print("test get_point_by_X (odd Y):", C.get_point_by_X(Q1.x, 1)); + print("test is on curve?:", C.contains(Point(Q1.x, C.getY(Q1.x,0))), C.contains(C.get_point_by_X(Q1.x,1))); + print("test is on curve (pow_mod)?:", C.contains(Point(Q1.x, C.getY(Q1.x,0))), C.contains(C.get_point_by_X(Q1.x,1))); + print("_____________________") +#end function + +run_test(); #result of one test just as demo + +""" +# test many random points with many iterations. +import time #to using interval +interval = 0.5; #seconds +# run tests +print ("Start : %s" % time.ctime()) +for i in range(0, 100): + print ("continue : %s" % time.ctime()) + time.sleep( interval ) # wait... + run_test(); # repeat test again, after some time +print ("End : %s" % time.ctime()) +""" \ No newline at end of file