├── ECC.py ├── LICENSE.txt ├── README.md ├── mini_ecdsa.py ├── tests_ECC.py └── tests_mini_ecdsa.py /ECC.py: -------------------------------------------------------------------------------- 1 | #mini_ecdsa.py must to be in the same folder with this file. 2 | from mini_ecdsa import *; #this script have dependency from mini_ecdsa.py, and can working with functions, which contains there. 3 | 4 | ''' 5 | Elliptic-Curve Cryptography (ECC) implementation - draft version. 6 | 7 | To make encryption and decryption messages, on the elliptic curve in finite field, 8 | need to encode and decode this data, as a points, which are contains on this elliptic-curve. 9 | 10 | If EC, like secp256k1, have the "n" unique points, and if "n" - this is a "prime number", 11 | then in the "semigroup" of "abel-group on this EC", there is "(n-1)/2" unique points with "unique coodinates". 12 | In the "second semigroup" of this EC, there is a points, which are "inverted" of the points in the first semigroup. 13 | This "inverted points" have the "same X-coodinates", but "parity of Y-coorinate" is inverted. 14 | 15 | 16 | """ 17 | # See the following example code: 18 | # Generate all points for small curve 19 | C = CurveOverFp(0, 0, 7, 211); #(y^2) mod p = (x^3 + 7) mod p 20 | P = Point(150, 22); #generator point, which "contains" on the curve. 21 | n = C.order(P) #points on the curve. 22 | print("\n\nSmall curve: "+str(C), "\nP: Point"+str(P), "\npoints: n = "+str(n)); 23 | 24 | for i in range(0, n): 25 | point = C.mult(P, i); 26 | print(str(i)+" * P = "+str(point)+"\t\tis on curve: "+str(C.contains(point)),"; y_parity:", (point.y%2)); 27 | """ 28 | 29 | quote: 30 | ... 31 | 95 * P = (180,38) is on curve: True ; y_parity: 0 32 | 96 * P = (187,113) is on curve: True ; y_parity: 1 33 | 97 * P = (22,59) is on curve: True ; y_parity: 1 34 | 98 * P = (126,37) is on curve: True ; y_parity: 1 35 | 99 * P = (31,141) is on curve: True ; y_parity: 1 36 | 100 * P = (31,70) is on curve: True ; y_parity: 0 37 | 101 * P = (126,174) is on curve: True ; y_parity: 0 38 | 102 * P = (22,152) is on curve: True ; y_parity: 0 39 | 103 * P = (187,98) is on curve: True ; y_parity: 0 40 | 104 * P = (180,173) is on curve: True ; y_parity: 1 41 | ... 42 | 43 | As you can see, points (99*P and 100*P, 98*P and 101*P, ..., etc...) 44 | have the same "X-coordinates", but different "Y-coordinates", and the "parity of Y" for this points are inverted. 45 | That means, in one semigroup, there is maximum (n-1)/2 unique "X-coordinates", with values from 0 up to p (211). 46 | Also, that means, each point from (0 up to n) "can be encoded" as "one number": (x*2 + y_parity_bit). 47 | 48 | But, not all nubmers from [0-p] can be encoded as one point, which contains on elliptic curve. 49 | And... Each number from 0-p can be writted as: x = k*c + r, 50 | 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); 51 | That means, each number from 0 to p, can be divided to 2, 52 | 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. 53 | Else, possible to continue divide the number, with add sequence of remainders (one from excluded points - 0 or 1). 54 | 55 | In this case, each number can be encoded as sequence of the points on elliptic cutve in finite field. 56 | Decoding is the reversive operation, because all points are contains on the curve, and have specified X and Y coordinates. 57 | 58 | After encoding the message on elliptic curve, result array with points can be encrypted, 59 | by multiply all points to secret scalar contant. 60 | k*P = Q; 61 | Reversive operation for decripting the points, is division the result-points for this scalar constant: 62 | Q/k = Q * k^(-1) mod p = Q * mult_inv(k, n); 63 | 64 | Meaning this all, each message can be splitted by blocks with values from 0 up to p, 65 | because the coordinates for each point have values from 0 to p. 66 | 67 | The following code is implementation of ECC encryption and decryption (text and numbers), using small curve and secp256k1 (bitcoin elliptic-curve). 68 | 69 | Summarry: 70 | ____________________________________________________________________________ 71 | pre-defined: elliptic curve parameters; array with excluded points; key; 72 | ciphertext: array with numbers or ciphertext string 73 | message: number or text_string 74 | encryption: message -> array with points * key -> array with numbers -> ciphertext string = ciphertext; 75 | decryption: ciphertext -> array with numbers -> array with points / key -> decode array with points = message. 76 | ____________________________________________________________________________ 77 | To exchange the key, can be used Elliptic-Curve Diffie-Hellman (ECDH): https://github.com/username1565/ecdh 78 | See working demo - here: https://username1565.github.io/ECDH/ 79 | ''' 80 | 81 | """ 82 | #example code to encode text to hex and decode hex to text. 83 | 84 | import binascii; 85 | m_hex = binascii.hexlify(m.encode("utf-8")); 86 | print("m_hex: ", m_hex); #hex 87 | 88 | decrypted_message = binascii.unhexlify(m_hex).decode('utf8') 89 | print("decrypted_message: ", decrypted_message); #text 90 | """ 91 | 92 | import random; #need to generate points and numbers, using random.randrange() 93 | 94 | #Generate array with unique excluded points. 95 | #This points need to encode all numbers from 0 up to p, as points, which contains on elliptic-curve. 96 | def generate_excluded_points(C, N='random'): 97 | N = random.randrange(3, C.char) if (N=='random' or (not(isinstance(N, int)))) else N; 98 | 99 | if(N<3): 100 | print("Mimumum 3 points needed."); 101 | N = 3; 102 | 103 | array = []; 104 | 105 | in_array = 0; 106 | while(len(array)10 and len(array)>3): #not lesser than 3 points must be generated 2 minimum + 1 as marker. 109 | print("Too many points already exists in array. Return array."); 110 | break; 111 | random_x = random.randrange(0, C.char); 112 | random_y_parity = random.randrange(0, 2); 113 | point = C.get_point_by_X(random_x, random_y_parity); 114 | is_on_curve = C.contains(point); 115 | if(is_on_curve and (not(point in array)) and (point.inf==False)): #points on curve, whithout duplicates, and O 116 | array.append(point); 117 | elif(point.inf==True): #if O - remove it. 118 | array.pop(0); 119 | elif(not(point in array)): #if already exists 120 | in_array+=1; 121 | # print("point", point, " already exists in array. Skip."); 122 | continue; 123 | elif(is_on_curve==False): #if not contains. 124 | # print("point", point, " not contains on curve. Skip."); 125 | continue; 126 | 127 | #show array in console. 128 | print("\nexcluded_points_array contains", len(array), "points."); 129 | 130 | print("\nexcluded_points_array = [\n", end = ''); 131 | for i in range(0, len(array)): 132 | is_on_curve = C.contains(array[i]); 133 | print("\tPoint"+str(array[i])+(", " if i!=len(array)-1 else "")+"\n", end = ''); 134 | print("];\n\n"); 135 | 136 | #input("Array with points was been generated. Press Enter to continue...") #pause and continue after press any key... 137 | return array; 138 | 139 | # By default key = 1, and points just will be encoded. 140 | # Else, if key!==0 and 0=", C.char, "break;"); 145 | return []; 146 | else: 147 | temp_point = C.get_point_by_X(x//2, x%2); 148 | encrypted_temp_point = C.mult(temp_point, key); 149 | is_on_curve = C.contains(encrypted_temp_point); 150 | temp_point_was_found_in_excluded_points = False; 151 | for item in range(0, len(excluded_points_array)): 152 | if( ( temp_point.x == excluded_points_array[item].x ) and ( temp_point.y == excluded_points_array[item].y )): 153 | temp_point_was_found_in_excluded_points = True; 154 | break; 155 | else: 156 | continue; 157 | if((is_on_curve) and (temp_point_was_found_in_excluded_points == False) and (encrypted_temp_point.inf==False)): 158 | encoded.append( int(int(encrypted_temp_point.x*2)+int(encrypted_temp_point.y%2)) ); 159 | else: 160 | remainder_index = int(x % (len(excluded_points_array)-1)); 161 | excluded_point = excluded_points_array[remainder_index]; 162 | excluded_point = C.mult(excluded_point, key); 163 | excluded_point_as_int = int(int(excluded_point.x*2)+int(excluded_point.y%2)); 164 | encoded.append(excluded_point_as_int); 165 | new_x_coordinate = int((x-remainder_index)//(len(excluded_points_array)-1)); 166 | if(new_x_coordinate == 0): 167 | encoded.append(0); 168 | else: 169 | new_sequence = encode_or_and_encrypt(C, new_x_coordinate, excluded_points_array, key); 170 | for i in range(0, len(new_sequence)): 171 | encoded.append(new_sequence[i]); 172 | return encoded; 173 | 174 | 175 | import math; #need for math.pow() 176 | # By default key = 1, and points just will be encoded. 177 | # Else, if key!==0 and 0 to string"; 298 | string = array_to_string(array); 299 | print("string: ", string); 300 | 301 | #back "string -> to array" ??? 302 | array2 = array_from_string(string); 303 | print("array2", array2); 304 | print("array==array2", array==array2); 305 | """ 306 | #____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ 307 | 308 | 309 | #import random #already imported. 310 | import string 311 | 312 | def randomString(stringLength=10): #generate random string with specified length 313 | """Generate a random string of fixed length """ 314 | letters = string.ascii_lowercase 315 | return ''.join(random.choice(letters) for i in range(stringLength)) 316 | 317 | """ 318 | #usage: 319 | print ("Random String is ", randomString() ) 320 | print ("Random String is ", randomString(10) ) 321 | print ("Random String is ", randomString(10) ) 322 | """ 323 | 324 | # Run tests_mini_ecdsa.py to test the functions in included mini_ecdsa.py 325 | # Run tests_ECC.py to test Elliptic-Curve Encryption functions from this ECC.py 326 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mini_ecdsa 2 | =============== 3 | 4 | Arithmetic on elliptic curves and introduction to ECDSA in Python. 5 | 6 | *Disclaimer*: This module is a tool for learning about elliptic curves and elliptic curve cryptography. It provides a fully functional implementation of ECDSA, but don't use it as anything other than a sandbox. There are many [subtle and important implementation details](http://safecurves.cr.yp.to/index.html) that I haven't thought about. 7 | 8 | You can find a really nice introduction to elliptic curve cryptography on [Andrea Corbellini's blog](http://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/). 9 | 10 | 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. 11 | 12 | ``` 13 | >>> exec(open("./mini_ecdsa.py").read()) 14 | ``` 15 | 16 | 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. 17 | 18 | ``` 19 | >>> C = CurveOverFp(2, 0, 1, 7) 20 | y^2 = x^3 + 2x^2 + 1 over F_7 21 | ``` 22 | 23 | To see a list of all finite order rational points on the curve, use `C.show_points()`. This will produce a nicely printed set of points. To return a list of point objects, use `C.get_points()`. *Warning:* These functions are very computationally intensive. Use them only on curves with small discriminant, or when working over a small finite field. 24 | 25 | ``` 26 | >>> C.show_points() 27 | ['Inf', '(0,1)', '(0,6)', '(1,2)', '(1,5)', '(3,2)', '(3,5)', '(5,1)', '(5,6)', '(6,3)', '(6,4)'] 28 | ``` 29 | 30 | Calling `C.torsion_group()` will classify the group of finite order rational points on a curve defined over Q, with the help of [Mazur's theorem](https://en.wikipedia.org/wiki/Torsion_conjecture#Elliptic_curves). Let's say we want to see the group of torsion points on the curve y^2 = x^3 + x + 2. 31 | 32 | ``` 33 | >>> C = CurveOverQ(0, 1, 2) 34 | y^2 = x^3 + x + 2 over Q 35 | >>> C.torsion_group() 36 | Z/4Z 37 | ['Inf', '(-1,0)', '(1,2)', '(1,-2)'] 38 | ``` 39 | 40 | In this case, the torsion points form a cyclic group of order four. You can also mess around with arithmetic on the curve by adding points, multiplying them by integers (remember multiplication in this context means repeated addition), and looking at the subgroup generated by a given point. 41 | 42 | ``` 43 | >>> C = CurveOverQ(0, 1, 2) 44 | y^2 = x^3 + x + 2 over Q 45 | >>> P = Point(-1,0) 46 | >>> Q = Point(1,2) 47 | >>> C.contains(P) 48 | True 49 | >>> C.contains(Q) 50 | True 51 | >>> print(C.add(P,Q)) 52 | (1,-2) 53 | >>> print(C.mult(Q,4)) 54 | Inf 55 | >>> C.generate(Q) 56 | ['Inf', '(-1,0)', '(1,2)', '(1,-2)'] 57 | ``` 58 | 59 | ECDSA is a digital signature scheme that uses elliptic curves. It's part of SSL/TLS and so you use it every day (click the green lock next to the url in your browser). Another of its best known uses is in Bitcoin, where spending money amounts to generating a valid ECDSA signature. 60 | 61 | To use ECDSA, we need to publicly agree on a curve over a finite field (this module works exclusively with prime characteristic fields) along with a distinguished point that generates a subgroup of prime order. Why the prime order requirement on the subgroup? As part of the signing process, we'll need to find a multiplicative inverse, and the prime order requirement guarantees this will work. 62 | 63 | *Tiny Example*: Consider P = (1341,854) on the curve y^2 = x^3 + x + 1 over the field with 2833 elements. 64 | 65 | ``` 66 | >>> C = CurveOverFp(0, 1, 1, 2833) 67 | y^2 = x^3 + x + 1 over F_2833 68 | >>> P = Point(1341,854) 69 | >>> C.contains(P) 70 | True 71 | >>> C.order(P) 72 | 131 73 | ``` 74 | 75 | Thus, P is on the curve, and it generates a subgroup of order 131, which is prime. The curve C, the point P, and the order of P are all public information. 76 | 77 | To sign a message, create a private-public keypair by calling `generate_keypair`. This keypair will consist of a randomly generated positive integer d smaller than the order of P, and a point Q = dP. The pair is returned as a tuple and printed. Computing Q given d and P can be done very quickly (this module uses the [double and add method](https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Double-and-add)), but at the moment, no one knows any effective and generally applicable method of computing d given P and Q. This is the [one-way function](https://en.wikipedia.org/wiki/One-way_function) that provides the theoretical security in all elliptic curve based cryptography. 78 | 79 | ``` 80 | >>> key = generate_keypair(C, P, 131) 81 | Priv key: d = 71 82 | Publ key: Q = (1449,1186) 83 | ``` 84 | 85 | Digital signatures generated by ECDSA consist of a public key Q as well as two positive integers, r and s, which are smaller than the order of P. These values are computed using the private key d and a hash of the message. In this implementation sha256 is the hash function being used. The signature is returned as a tuple by `sign` and also printed. 86 | 87 | ``` 88 | >>> msg = 'this is an important message' 89 | >>> sig = sign(msg.encode('utf-8'), C, P, 131, key) 90 | ECDSA sig: (Q, r, s) = ((2387,711), 125, 50) 91 | ``` 92 | 93 | 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. 94 | 95 | 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`. 96 | 97 | ``` 98 | >>> verify('this is an important message'.encode('utf-8'), C, P, 131, sig) 99 | True 100 | >>> verify('thiz is an important mossage'.encode('utf-8'), C, P, 131, sig) 101 | False 102 | ``` 103 | 104 | *Big Example*: The curve (over a given finite field with a distinguished point) used to verify Bitcoin transactions is called secp256k1. I've been working with this curve a lot, so the classmethods `CurveOverFp.secp256k1()` and `Point.secp256k1()` are provided to save time, but you can also do it the hard way. 105 | 106 | ``` 107 | >>> C = CurveOverFp(0, 0, 7, 2**256-2**32-2**9-2**8-2**7-2**6-2**4-1) 108 | y^2 = x^3 + 7 over F_115792089237316195423570985008687907853269984665640564039457584007908834671663 109 | >>> P = Point(55066263022277343669578718895168534326250603453777594175500187360389116729240,32670510020758816978083085130507043184471273380659243275938904335757337482424) 110 | >>> n = 115792089237316195423570985008687907852837564279074904382605163141518161494337 111 | ``` 112 | 113 | There are few noteworthy things about the order of P for secp256k1. Note that it's given, not calculated. That makes sense, since in order to find the order of a point (naively) we need to calculate iP for increasing i until we get the point at infinity. If that was actually feasible, we could crack public keys and recover d from Q in the same way, by calculating iP for increasing i until the result is Q. Calling `C.order(P)` will indeed calculate the order of P in this naive way, so don't do it unless you're prepared to wait a long time, and by long I mean universe ending long. 114 | 115 | The order of P is also about the same as the size of the field the curve is defined over. If you've studied elliptic curves before and know about the [Hasse bound](https://en.wikipedia.org/wiki/Hasse's_theorem_on_elliptic_curves) along with a little bit of group theory, you should be able to convince yourself that the subgroup generated by P is actually the entire set of points on the curve. This is good, it means the set of private keys (possible values for d) is as large as it can be on this curve. 116 | 117 | Generating keypairs, signing, and authenticating are all done exactly as in the tiny example. 118 | 119 | ``` 120 | >>> key = generate_keypair(C, P, n) 121 | Priv key: 50815196737043990229212712040447958865064188767262580693117504952509239687366 122 | Publ key: (60178516215593300273458789571475750050105656844208932820175446762050535381256,92933466624192676140900093650081093228918214155456856436706041935976250501492) 123 | >>> msg = 'this is an important message' 124 | >>> sig = sign(msg.encode('utf-8'), C, P, n, key) 125 | ECDSA sig: (Q, r, s) = ((101009753524284455683527059639760708650603091496216267843569752755013360588206,80820713767805695085573982412000542390137294612577964946170582917066926184868), 103388573995635080359749164254216598308788835304023601477803095234286494993683, 63310347199411496995536543411223361953377643452656880364442100146757805748153) 126 | >>> verify('this is an important message'.encode('utf-8'), C, P, n, sig) 127 | True 128 | >>> verify('this is a faked important message'.encode('utf-8'), C, P, n, sig) 129 | False 130 | ``` 131 | 132 | When you see these numbers in the wild, they are typically given as a sequence of hex bytes. In the case of Bitcoin, the private key d is stored in a Bitcoin wallet, while the public key Q goes through a [hashing procedure](https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses) and gets converted to base 58. That's what a Bitcoin address is, a point on the curve y^2 = x^3 + 7 over a really big finite field after being eaten by hash functions a few times and converted to base 58. Cool. 133 | 134 | Also included in this module are a few methods of solving Q = dP for d, i.e. cracking a public key and recovering the private key. The group defined by the curve and point given below is small enough for brute forcing to succeed, but large enough that the process takes a few minutes on a desktop computer, so it's a nice context for comparing these key cracking methods. 135 | 136 | ``` 137 | >>> C = CurveOverFp(0, 1, 7, 729787) 138 | y^2 = x^3 + x + 7 over F_729787 139 | >>> P = Point(1,3) 140 | >>> n = C.order(P) 141 | >>> (d, Q) = generate_keypair(C, P, n) 142 | Priv key: d = 692847 143 | Publ key: Q = (257099,102580) 144 | ``` 145 | 146 | The `crack_brute_force` function will simply try all possible values of d in increasing order until Q = dP. 147 | 148 | ``` 149 | >>> crack_brute_force(C, P, n, Q) 150 | Priv key: d = 692847 151 | Time: 177.963 secs 152 | ``` 153 | 154 | The baby-step giant-step method works by using a hash table to trade space for time. It's particularly easy to implement since python has hash tables nicely built in as dictionaries. 155 | 156 | ``` 157 | >>> crack_baby_giant(C, P, n, Q) 158 | Priv key: d = 692847 159 | Time: 0.356 secs 160 | ``` 161 | 162 | In this example, the baby-step giant-step method performs well, but in larger examples the memory requirements become problematic, and simply constructing the hash table can take an enourmous amount of time. At some point the time-space tradeoff becomes unfeasible however you slice it. 163 | 164 | Pollard's rho method manages to acheive the same aymptotic time complexity while eschewing the memory issues completely. It incorporates a clever idea called the [tortoise and hare algorithm](https://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare) to find two distinct linear combinations of P and Q that produce the same point, so aP + bQ = cP + dQ. Isolating Q yields the private key. The last argument to the function, `bits`, is used to create a small list of randomly generated linear combinations of P and Q, of length 2^bits. This list is then used to define an iterating function on the curve (for details, see section 4.1.2 of Menezes, Hankerson, and Vanstone's Guide to Elliptic Curve Cryptography). 165 | 166 | ``` 167 | >>> crack_rho(C, P, n, Q, 4) 168 | Priv key: d = 692847 169 | Time: 0.077 secs 170 | ``` 171 | 172 | Finally, there is a method to recover the private key from a pair of messages signed using the same value of k, called `crack_from_ECDSA_repeat_k`. This is a very quick calculation, using only a few lines of modular arithmetic and no iteration or arithmetic on the curve at all. 173 | -------------------------------------------------------------------------------- /mini_ecdsa.py: -------------------------------------------------------------------------------- 1 | #Elliptic curve basics, tools for finding rational points, and ECDSA implementation. 2 | #Brendan Cordy, 2015 3 | #modified for Python 3.2.5 4 | 5 | from fractions import Fraction 6 | from math import ceil, sqrt 7 | from random import SystemRandom, randrange 8 | from hashlib import sha256 9 | from time import clock 10 | 11 | def pow_mod(x, y, z): #fast pow mod (need to fast check is point on the curve on not) 12 | "Calculate (x ** y) % z efficiently." 13 | number = 1 14 | while y: 15 | if y & 1: 16 | number = number * x % z 17 | y >>= 1 18 | x = x * x % z 19 | return number 20 | 21 | #Affine Point (+Infinity) on an Elliptic Curve --------------------------------------------------- 22 | 23 | class Point(object): 24 | #Construct a point with two given coordindates. 25 | def __init__(self, x, y): 26 | self.x, self.y = x, y 27 | self.inf = False 28 | 29 | #Construct the point at infinity. 30 | @classmethod 31 | def atInfinity(cls): 32 | P = cls(0, 0) 33 | P.inf = True 34 | return P 35 | 36 | #The secp256k1 generator. 37 | @classmethod 38 | def secp256k1(cls): 39 | return cls(55066263022277343669578718895168534326250603453777594175500187360389116729240, 40 | 32670510020758816978083085130507043184471273380659243275938904335757337482424) 41 | 42 | def __str__(self): 43 | if self.inf: 44 | return 'Inf' 45 | else: 46 | return '(' + str(self.x) + ',' + str(self.y) + ')' 47 | 48 | def __eq__(self,other): 49 | if self.inf: 50 | return other.inf 51 | elif other.inf: 52 | return self.inf 53 | else: 54 | return self.x == other.x and self.y == other.y 55 | 56 | def is_infinite(self): 57 | return self.inf 58 | 59 | #Elliptic Curves over any Field ------------------------------------------------------------------ 60 | 61 | class Curve(object): 62 | #Set attributes of a general Weierstrass cubic y^2 = x^3 + ax^2 + bx + c over any field. 63 | def __init__(self, a, b, c, char, exp): 64 | self.a, self.b, self.c = a, b, c 65 | self.char, self.exp = char, exp 66 | print(self) 67 | 68 | def __str__(self): 69 | #Cases for 0, 1, -1, and general coefficients in the x^2 term. 70 | if self.a == 0: 71 | aTerm = '' 72 | elif self.a == 1: 73 | aTerm = ' + x^2' 74 | elif self.a == -1: 75 | aTerm = ' - x^2' 76 | elif self.a < 0: 77 | aTerm = " - " + str(-self.a) + 'x^2' 78 | else: 79 | aTerm = " + " + str(self.a) + 'x^2' 80 | #Cases for 0, 1, -1, and general coefficients in the x term. 81 | if self.b == 0: 82 | bTerm = '' 83 | elif self.b == 1: 84 | bTerm = ' + x' 85 | elif self.b == -1: 86 | bTerm = ' - x' 87 | elif self.b < 0: 88 | bTerm = " - " + str(-self.b) + 'x' 89 | else: 90 | bTerm = " + " + str(self.b) + 'x' 91 | #Cases for 0, 1, -1, and general coefficients in the constant term. 92 | if self.c == 0: 93 | cTerm = '' 94 | elif self.c < 0: 95 | cTerm = " - " + str(-self.c) 96 | else: 97 | cTerm = " + " + str(self.c) 98 | #Write out the nicely formatted Weierstrass equation. 99 | self.eq = 'y^2 = x^3' + aTerm + bTerm + cTerm 100 | #Print prettily. 101 | if self.char == 0: 102 | return self.eq + ' over Q' 103 | elif self.exp == 1: 104 | return self.eq + ' over ' + 'F_' + str(self.char) 105 | else: 106 | return self.eq + ' over ' + 'F_' + str(self.char) + '^' + str(self.exp) 107 | 108 | #Compute the discriminant. 109 | def discriminant(self): 110 | a, b, c = self.a, self.b, self.c 111 | return -4*a*a*a*c + a*a*b*b + 18*a*b*c - 4*b*b*b - 27*c*c 112 | 113 | #Compute the order of a point on the curve. 114 | def order(self, P): 115 | Q = P 116 | orderP = 1 117 | #Add P to Q repeatedly until obtaining the identity (point at infinity). 118 | while not Q.is_infinite(): 119 | Q = self.add(P,Q) 120 | orderP += 1 121 | return orderP 122 | 123 | #List all multiples of a point on the curve. 124 | def generate(self, P): 125 | Q = P 126 | orbit = [str(Point.atInfinity())] 127 | #Repeatedly add P to Q, appending each (pretty printed) result. 128 | while not Q.is_infinite(): 129 | orbit.append(str(Q)) 130 | Q = self.add(P,Q) 131 | return orbit 132 | 133 | #Double a point on the curve. 134 | def double(self, P): 135 | return self.add(P,P) 136 | 137 | #Add P to itself k times. 138 | def mult(self, P, k): 139 | if P.is_infinite(): 140 | return P 141 | elif k == 0: 142 | return Point.atInfinity() 143 | elif k < 0: 144 | return self.mult(self.invert(P), -k) 145 | else: 146 | #Convert k to a bitstring and use peasant multiplication to compute the product quickly. 147 | b = bin(k)[2:] 148 | return self.repeat_additions(P, b, 1) 149 | 150 | #Add efficiently by repeatedly doubling the given point, and adding the result to a running 151 | #total when, after the ith doubling, the ith digit in the bitstring b is a one. 152 | def repeat_additions(self, P, b, n): 153 | if b == '0': 154 | return Point.atInfinity() 155 | elif b == '1': 156 | return P 157 | elif b[-1] == '0': 158 | return self.repeat_additions(self.double(P), b[:-1], n+1) 159 | elif b[-1] == '1': 160 | return self.add(P, self.repeat_additions(self.double(P), b[:-1], n+1)) 161 | 162 | #Returns a pretty printed list of points. 163 | def show_points(self): 164 | return [str(P) for P in self.get_points()] 165 | 166 | #Elliptic Curves over Q -------------------------------------------------------------------------- 167 | 168 | class CurveOverQ(Curve): 169 | #Construct a Weierstrass cubic y^2 = x^3 + ax^2 + bx + c over Q. 170 | def __init__(self, a, b, c): 171 | Curve.__init__(self, a, b, c, 0, 0) 172 | 173 | def contains(self, P): 174 | if P.is_infinite(): 175 | return True 176 | else: 177 | return P.y*P.y == P.x*P.x*P.x + self.a*P.x*P.x + self.b*P.x + self.c 178 | 179 | def get_points(self): 180 | #Start with the point at infinity. 181 | points = [Point.atInfinity()] 182 | #The only possible y values are divisors of the discriminant. 183 | for y in divisors(self.discriminant()): 184 | #Each possible y value yields a monic cubic polynomial in x, whose roots 185 | #must divide the constant term. 186 | const_term = self.c - y*y 187 | if const_term != 0: 188 | for x in divisors(const_term): 189 | P = Point(x,y) 190 | if 0 == x*x*x + self.a*x*x + self.b*x + const_term and self.has_finite_order(P): 191 | points.append(P) 192 | #If the constant term is zero, factor out x and look for rational roots 193 | #of the resulting quadratic polynomial. Any such roots must divide b. 194 | elif self.b != 0: 195 | for x in divisors(self.b): 196 | P = Point(x,y) 197 | if 0 == x*x*x+self.a*x*x+self.b*x+const_term and self.has_finite_order(P): 198 | points.append(P) 199 | #If the constant term and b are both zero, factor out x^2 and look for rational 200 | #roots of the resulting linear polynomial. Any such roots must divide a. 201 | elif self.a != 0: 202 | for x in divisors(self.a): 203 | P = Point(x,y) 204 | if 0 == x*x*x+self.a*x*x+self.b*x+const_term and self.has_finite_order(P): 205 | points.append(P) 206 | #If the constant term, b, and a are all zero, we have 0 = x^3 + c - y^2 with 207 | #const_term = c - y^2 = 0, so (0,y) is a point on the curve. 208 | else: 209 | points.append(Point(0,y)) 210 | #Ensure that there are no duplicates in our list of points. 211 | unique_points = [] 212 | for P in points: 213 | addP = True 214 | for Q in unique_points: 215 | if P == Q: 216 | addP = False 217 | if addP: 218 | unique_points.append(P) 219 | return unique_points 220 | 221 | def invert(self, P): 222 | if P.is_infinite(): 223 | return P 224 | else: 225 | return Point(P.x, -P.y) 226 | 227 | def add(self, P_1, P_2): 228 | #Compute the differences in the coordinates. 229 | y_diff = P_2.y - P_1.y 230 | x_diff = P_2.x - P_1.x 231 | #Cases involving the point at infinity. 232 | if P_1.is_infinite(): 233 | return P_2 234 | elif P_2.is_infinite(): 235 | return P_1 236 | #Case for adding an affine point to its inverse. 237 | elif x_diff == 0 and y_diff != 0: 238 | return Point.atInfinity() 239 | #Case for adding an affine point to itself. 240 | elif x_diff == 0 and y_diff == 0: 241 | #If the point is on the x-axis, there's a vertical tangent there (assuming 242 | #the curve in nonsingular) so we obtain the point at infinity. 243 | if P_1.y == 0: 244 | return Point.atInfinity() 245 | #Otherwise the result is an affine point on the curve we can arrive at by 246 | #following the tangent line, whose slope is given below. 247 | else: 248 | ld = Fraction(3*P_1.x*P_1.x + 2*self.a*P_1.x + self.b, 2*P_1.y) 249 | #Case for adding two distinct affine points, where we compute the slope of 250 | #the secant line through the two points. 251 | else: 252 | ld = Fraction(y_diff, x_diff) 253 | #Use the slope of the tangent line or secant line to compute the result. 254 | nu = P_1.y - ld*P_1.x 255 | x = ld*ld - self.a -P_1.x - P_2.x 256 | y = -ld*x - nu 257 | return Point(x,y) 258 | 259 | #Use the Nagell-Lutz Theorem and Mazur's Theorem to potentially save time. 260 | def order(self, P): 261 | Q = P 262 | orderP = 1 263 | #Add P to Q repeatedly until obtaining the point at infinity. 264 | while not Q.is_infinite(): 265 | Q = self.add(P,Q) 266 | orderP += 1 267 | #If we ever obtain non integer coordinates, the point has infinite order. 268 | if Q.x != int(Q.x) or Q.y != int(Q.y): 269 | return -1 270 | #Moreover, all finite order points have order at most 12. 271 | if orderP > 12: 272 | return -1 273 | return orderP 274 | 275 | def has_finite_order(self, P): 276 | return not self.order(P) == -1 277 | 278 | def torsion_group(self): 279 | highest_order = 1 280 | #Find the rational point with the highest order. 281 | for P in self.get_points(): 282 | if self.order(P) > highest_order: 283 | highest_order = self.order(P) 284 | #If this point generates the entire torsion group, the torsion group is cyclic. 285 | if highest_order == len(self.get_points()): 286 | print('Z/' + str(highest_order) + 'Z') 287 | #If not, by Mazur's Theorem the torsion group must be a direct product of Z/2Z 288 | #with the cyclic group generated by the highest order point. 289 | else: 290 | print('Z/2Z x ' + 'Z/' + str(highest_order) + 'Z') 291 | print(C.show_points()) 292 | 293 | #Elliptic Curves over Prime Order Fields --------------------------------------------------------- 294 | 295 | class CurveOverFp(Curve): 296 | #Construct a Weierstrass cubic y^2 = x^3 + ax^2 + bx + c over Fp. 297 | def __init__(self, a, b, c, p): 298 | Curve.__init__(self, a, b, c, p, 1) 299 | 300 | #The secp256k1 curve. 301 | @classmethod 302 | def secp256k1(cls): 303 | return cls(0, 0, 7, 2**256-2**32-2**9-2**8-2**7-2**6-2**4-1) 304 | 305 | def contains(self, P, pow_mod=0): 306 | if P.is_infinite(): 307 | return True 308 | elif(pow_mod==0): # check: (y^2) mod p == (x^3 + ax^2 + bx + c) mod p 309 | 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 310 | else: # check this faster, using pow_mod 311 | 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 312 | 313 | def get_points(self): 314 | #Start with the point at infinity. 315 | points = [Point.atInfinity()] 316 | 317 | #Just brute force the rest. 318 | for x in range(self.char): 319 | for y in range(self.char): 320 | P = Point(x,y) 321 | if (y*y) % self.char == (x*x*x + self.a*x*x + self.b*x + self.c) % self.char: 322 | points.append(P) 323 | return points 324 | 325 | def invert(self, P): #additive inversion 326 | if P.is_infinite(): 327 | return P 328 | else: 329 | return Point(P.x, -P.y % self.char) #-Q = (x_Q, -y_Q%p) 330 | 331 | def add(self, P_1, P_2): 332 | #Adding points over Fp and can be done in exactly the same way as adding over Q, 333 | #but with of the all arithmetic now happening in Fp. 334 | y_diff = (P_2.y - P_1.y) % self.char 335 | x_diff = (P_2.x - P_1.x) % self.char 336 | if P_1.is_infinite(): 337 | return P_2 338 | elif P_2.is_infinite(): 339 | return P_1 340 | elif x_diff == 0 and y_diff != 0: 341 | return Point.atInfinity() 342 | elif x_diff == 0 and y_diff == 0: 343 | if P_1.y == 0: 344 | return Point.atInfinity() 345 | else: 346 | ld = ((3*P_1.x*P_1.x + 2*self.a*P_1.x + self.b) * mult_inv(2*P_1.y, self.char)) % self.char 347 | else: 348 | ld = (y_diff * mult_inv(x_diff, self.char)) % self.char 349 | nu = (P_1.y - ld*P_1.x) % self.char 350 | x = (ld*ld - self.a - P_1.x - P_2.x) % self.char 351 | y = (-ld*x - nu) % self.char 352 | return Point(x,y) 353 | 354 | def subtract(self, point, another): #C.subtract(from, minus) 355 | if(another.is_infinite()): 356 | return point; 357 | else: 358 | return self.add(point, self.invert(another)); # (P - Q) = P + (-Q) 359 | 360 | def divide_point(self, point, k, n): 361 | # print("k", k, "n", n, "mult_inv(k, n)", mult_inv(k, n)); 362 | return self.mult(point, mult_inv(k, n)) # (P / k) = P * (k^-1 mod n) 363 | 364 | def getY(self, X, Y_parity_bit=0): # for each X there is two Y on curve, odd and even. 365 | a = (pow_mod(X, 3, self.char) + 7) % self.char # a = ( ( ( (x^3) mod n ) ax^2 + bx + c ) mod n ) 366 | Y = pow_mod(a, (self.char+1)//4, self.char) # y = a^{(n+1)//4} mod n 367 | if Y % 2 != Y_parity_bit: 368 | Y = -Y % self.char # invert Y 369 | # return Y; 370 | return Y%self.char; 371 | 372 | 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. 373 | # return Point(X, self.getY(X, Y_parity_bit)); #return this. 374 | return Point(X%self.char, self.getY(X, Y_parity_bit)); #return this. 375 | 376 | #Elliptic Curves over Prime Power Order Fields --------------------------------------------------- 377 | 378 | class CurveOverFq(Curve): 379 | #Construct a Weierstrass cubic y^2 = x^3 + ax^2 + bx + c over Fp^n. 380 | def __init__(self, a, b, c, p, n, irred_poly): 381 | self.irred_poly = irred_poly 382 | Curve.__init__(self, a, b, c, p, n) 383 | 384 | #TODO: Implement it! 385 | 386 | #Number Theoretic Functions ---------------------------------------------------------------------- 387 | 388 | def divisors(n): 389 | divs = [0] 390 | for i in range(1, abs(n) + 1): 391 | if n % i == 0: 392 | divs.append(i) 393 | divs.append(-i) 394 | return divs 395 | 396 | #Extended Euclidean algorithm. 397 | def euclid(sml, big): 398 | #When the smaller value is zero, it's done, gcd = b = 0*sml + 1*big. 399 | if sml == 0: 400 | return (big, 0, 1) 401 | else: 402 | #Repeat with sml and the remainder, big%sml. 403 | g, y, x = euclid(big % sml, sml) 404 | #Backtrack through the calculation, rewriting the gcd as we go. From the values just 405 | #returned above, we have gcd = y*(big%sml) + x*sml, and rewriting big%sml we obtain 406 | #gcd = y*(big - (big//sml)*sml) + x*sml = (x - (big//sml)*y)*sml + y*big. 407 | return (g, x - (big//sml)*y, y) 408 | 409 | # Compute the multiplicative modular inverse mod n of a with 0 < a < n. 410 | def mult_inv(a, n): # return b, for which: a * b === 1 (mod n), or return tuple with gcd 411 | g, x, y = euclid(a, n) 412 | #If gcd(a,n) is not one, then a has no multiplicative inverse. 413 | if g != 1: 414 | #raise ValueError('multiplicative inverse does not exist for value a mod n =', a, "mod", n); 415 | #return str('multiplicative inverse does not exist for value a mod n = ')+str(a)+str(" mod ")+str(n); 416 | return ("gcd!=1", g); # if value have no modular inverse - don't show error and just return tuple, with gcd. 417 | #If gcd(a,n) = 1, and gcd(a,n) = x*a + y*n, x is the multiplicative inverse of a. 418 | else: 419 | return x % n 420 | 421 | #ECDSA functions --------------------------------------------------------------------------------- 422 | 423 | #Use sha256 to hash a message, and return the hash value as an interger. 424 | def hash(message): 425 | return int(sha256(message).hexdigest(), 16) 426 | 427 | #Hash the message and return integer whose binary representation is the the L leftmost bits 428 | #of the hash value, where L is the bit length of n. 429 | def hash_and_truncate(message, n): 430 | h = hash(message) 431 | b = bin(h)[2:len(bin(n))] 432 | return int(b, 2) 433 | 434 | #Generate a keypair using the point P of order n on the given curve. The private key is a 435 | #positive integer d smaller than n, and the public key is Q = dP. 436 | def generate_keypair(curve, P, n): 437 | sysrand = SystemRandom() 438 | d = sysrand.randrange(1, n) 439 | Q = curve.mult(P, d) 440 | print("Priv key: d = " + str(d)) 441 | print("Publ key: Q = " + str(Q)) 442 | return (d, Q) 443 | 444 | #Create a digital signature for the string message using a given curve with a distinguished 445 | #point P which generates a prime order subgroup of size n. 446 | def sign(message, curve, P, n, keypair): 447 | #Extract the private and public keys, and compute z by hashing the message. 448 | d, Q = keypair 449 | z = hash_and_truncate(message, n) 450 | #Choose a randomly selected secret point kP then compute r and s. 451 | r, s = 0, 0 452 | while r == 0 or s == 0: 453 | k = 4 454 | R = curve.mult(P, k) 455 | r = R.x % n 456 | s = (mult_inv(k, n) * (z + r*d)) % n 457 | print('ECDSA sig: (Q, r, s) = (' + str(Q) + ', ' + str(r) + ', ' + str(s) + ')') 458 | return (Q, r, s) 459 | 460 | #Verify the string message is authentic, given an ECDSA signature generated using a curve with 461 | #a distinguished point P that generates a prime order subgroup of size n. 462 | def verify(message, curve, P, n, sig): 463 | Q, r, s = sig 464 | #Confirm that Q is on the curve. 465 | if Q.is_infinite() or not curve.contains(Q): 466 | return False 467 | #Confirm that Q has order that divides n. 468 | if not curve.mult(Q,n).is_infinite(): 469 | return False 470 | #Confirm that r and s are at least in the acceptable range. 471 | if r > n or s > n: 472 | return False 473 | #Compute z in the same manner used in the signing procedure, 474 | #and verify the message is authentic. 475 | z = hash_and_truncate(message, n) 476 | w = mult_inv(s, n) % n 477 | u_1, u_2 = z * w % n, r * w % n 478 | C_1, C_2 = curve.mult(P, u_1), curve.mult(Q, u_2) 479 | C = curve.add(C_1, C_2) 480 | return r % n == C.x % n 481 | 482 | #Key Cracking Functions -------------------------------------------------------------------------- 483 | 484 | #Find d for which Q = dP by simply trying all possibilities 485 | def crack_brute_force(curve, P, n, Q, start=0, show_each=0): #it can be parallelized 486 | start_time = clock() 487 | print("start: ", start, "up to:", n); 488 | for d in range(start, n): 489 | 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. 490 | if curve.mult(P,d) == Q: 491 | end_time = clock() 492 | print("Priv key: d = " + str(d)) 493 | print("Time: " + str(round(end_time - start_time, 3)) + " secs") 494 | break 495 | 496 | #Find d for which Q = dP using the baby-step giant-step algortihm. 497 | def crack_baby_giant(curve, P, n, Q, m=0): #m can be specified, 0 by default sqrt(n) 498 | start_time = clock() 499 | 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. 500 | #else - O(n/m) multiplies, and eating m memory for storing all points for baby-steps. 501 | #Build a hash table with all bP with 0 < b < m using a dictionary. The dicitonary value 502 | #stores b so that it can be quickly recovered after a matching giant step hash. 503 | baby_table = {} 504 | for b in range(m): 505 | bP = curve.mult(P,b) 506 | baby_table[str(bP)] = b 507 | #Check if Q - gmP is in the hash table for all 0 < g < m. If we get such a matching hash, 508 | #we have Q - gmP = bP, so extract b from the dictionary, then Q = (b + gm)P. 509 | for g in range((int(n/m)+1)): 510 | R = curve.add(Q, curve.invert(curve.mult(P, g*m))) 511 | if str(R) in baby_table.keys(): 512 | b = baby_table[str(R)] 513 | end_time = clock() 514 | print("Priv key: d = " + str((b + g*m) % n)) 515 | print("Time: " + str(round(end_time - start_time, 3)) + " secs") 516 | break 517 | 518 | #Find d for which Q = dP using Pollard's rho algorithm. 519 | def crack_rho(curve, P, n, Q, bits): 520 | start_time = clock() 521 | while True: 522 | R_list = [] 523 | #Compute 2^bits randomly selected linear combinations of P and Q, storing them as triples 524 | #of the form (aP + bQ, a, b) in R_list. 525 | for i in range(2**bits): 526 | a, b = randrange(0,n), randrange(0,n) 527 | R_list.append((curve.add(curve.mult(P,a), curve.mult(Q,b)), a, b)) 528 | #Compute a new random linear combination of P and Q to start the cycle-finding. 529 | aT, bT = randrange(0,n), randrange(0,n) 530 | aH, bH = aT, bT 531 | T = curve.add(curve.mult(P,aT), curve.mult(Q,bT)) 532 | H = curve.add(curve.mult(P,aH), curve.mult(Q,bH)) 533 | while True: 534 | #Advance the tortoise one step, by adding a point in R_list determined by the last b 535 | #bits in the binary explansion of the x coordinate of the current position. 536 | j = int(bin(T.x)[len(bin(T.x)) - bits : len(bin(T.x))], 2) 537 | T, aT, bT = curve.add(T, R_list[j][0]), (aT + R_list[j][1]) % n, (bT + R_list[j][2]) % n 538 | #Advance the hare two steps, again by adding points in R_list determined by the last 539 | #b bits in the binary explansion of the x coordinate of the current position. 540 | for i in range(2): 541 | j = int(bin(H.x)[len(bin(H.x)) - bits : len(bin(H.x))], 2) 542 | H, aH, bH = curve.add(H, R_list[j][0]), (aH + R_list[j][1]) % n, (bH + R_list[j][2]) % n 543 | #If the tortoise and hare arrive at the same point, a cycle has been found. 544 | if(T == H): 545 | break 546 | #It is possible that the tortoise and hare arrive at exactly the same linear combination. 547 | if bH == bT: 548 | end_time = clock() 549 | print("Rho failed with identical linear combinations") 550 | print(str(end_time - start_time) + " secs") 551 | else: 552 | end_time = clock() 553 | print("\nPollard-rho, results:"); 554 | modular_inverse = mult_inv((bH - bT) % n, n); #Some values have no modular inverse, and will be returned gcd. 555 | 556 | if isinstance(modular_inverse, int): 557 | modular_inverse = modular_inverse % n 558 | priv = ((aT - aH) * modular_inverse) % n; 559 | pub = curve.mult(P, priv); 560 | if(pub.__eq__(Q)): 561 | print("Priv key: d = " + str(priv) + str(", pub: ")+str(pub)); 562 | print("Time: " + str(round(end_time - start_time, 3)) + " secs") 563 | break; 564 | else: 565 | print("pub not equal Q", "pub", pub, "Q", Q); 566 | continue; 567 | elif ( (isinstance(modular_inverse, tuple)) and (modular_inverse[0] == "gcd!=1") ) : 568 | print("invalid modinv... gcd = ", modular_inverse[1], "Try again..."); 569 | continue; 570 | 571 | #Find d by exploiting two messages signed with the same value of k. 572 | def crack_from_ECDSA_repeat_k(curve, P, n, m1, sig1, m2, sig2): 573 | Q1, r1, s1 = sig1 574 | Q2, r2, s2 = sig2 575 | #Check that the two messages were signed with the same k. This check may pass even if m1 576 | #and m2 were signed with distinct k, in which case the value of d computed below will be 577 | #wrong, but this is a very unlikely scenario. 578 | if not r1 == r2: 579 | print("Messages signed with distinct k") 580 | else: 581 | z1 = hash_and_truncate(m1, n) 582 | z2 = hash_and_truncate(m2, n) 583 | k = (z1 - z2) * mult_inv((s1 - s2) % n, n) % n 584 | d = mult_inv(r1, n) * ((s1 * k) % n - z1) % n 585 | print("Priv key: d = " + str(d)) 586 | 587 | # Run tests_mini_ecdsa.py to test the functions in this mini_ecdsa.py 588 | # Run tests_ECC.py to test Elliptic-Curve Encryption functions from ECC.py -------------------------------------------------------------------------------- /tests_ECC.py: -------------------------------------------------------------------------------- 1 | #mini_ecdsa.py and ECC.py must to be in the same folder with this file. 2 | from ECC import *; #import function from ECC.py. All functions importing there too from mini_ecdsa.py. 3 | 4 | #______________________________________________________________________________________________________________________________ 5 | # tests Elliptic-curve encryption-decryption 6 | # initialization tests. 7 | 8 | print("_________________________________"); 9 | # initialization of elliptic curve 10 | 11 | ''' 12 | #smaller elliptic curve 13 | C = CurveOverFp(0, 0, 7, 211); 14 | P = Point(150, 22); 15 | print("\n\n"+"smaller curve:", C, ", generator point: ", P); 16 | n = C.order(P) 17 | print("n", n); 18 | #(d, Q) = generate_keypair(C, P, n); #limited value of d (max = n) 19 | #print("d", d, "Q", Q); 20 | ''' 21 | 22 | """ 23 | larger elliptic curve 24 | C = CurveOverFp(0, 1, 7, 729787) 25 | P = Point(1,3) 26 | n = 730819; 27 | """ 28 | 29 | 30 | #secp256k1 - this is a big elliptic curve, which is using bitcoin. 31 | C = CurveOverFp.secp256k1(); 32 | P = Point.secp256k1(); 33 | n = 115792089237316195423570985008687907852837564279074904382605163141518161494337; 34 | print("P = Point", P) 35 | print("n =", n); 36 | print("_________________________________"); 37 | 38 | #______________________________________________________________________________________________________________________________ 39 | 40 | # initialization of encryption-decryption key 41 | 42 | #import random #already imported. 43 | 44 | # 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); 45 | #key = 1; #no key, just encode and decode. This can be not specifiec and key=1 by default. 46 | #key = 2; #test with some key. 47 | 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) 48 | print("\nGenerated key for encryption-decryption: ", key); # show key. 49 | 50 | ''' 51 | print("_________________________________"); 52 | # change limit for recursion, if you see an error. 53 | import sys; 54 | print(sys.getrecursionlimit()) #default 1000, and this limit can be is reached. 55 | sys.setrecursionlimit(5000) #set this limit higher 56 | print(sys.getrecursionlimit()) #default 1000, and this limit can be is reached. 57 | ''' 58 | 59 | print("_________________________________"); 60 | 61 | 62 | # generate array with excluded points 63 | 64 | #Minumum 3 points must to be excluded. 65 | #Two for encoding-decoding numbers, that is not a coordinates of the points, which are contains on elliptic curve, 66 | #and one more point, as block-marker, to do descrimination the beginning of new encoded point. 67 | #if array contains k+1 points, then: 68 | #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. 69 | 70 | excluded_points_array = generate_excluded_points(C, 3); #3 points x = c*2 + r 71 | #excluded_points_array = generate_excluded_points(C, 4); #4 points x = c*3 + r 72 | #excluded_points_array = generate_excluded_points(C, 8); #8 points x = c*7 + r 73 | #excluded_points_array = generate_excluded_points(C, 'random'); #from 3 up to p-1 (C.char) 74 | 75 | 76 | print("_________________________________"); 77 | 78 | #______________________________________________________________________________________________________________________________ 79 | 80 | # test encryption-decryption text 81 | 82 | print("\n\ntest string encryption-decryption:\n"); 83 | #open_message = "test_text"; #utf-8 encoded string 84 | open_message = randomString(50); #random string 85 | #open_message = randomString(5); #shorter random string 86 | 87 | #print("open_message =", open_message); #show message 88 | c = text_encode(open_message, C, excluded_points_array, key); #encoded message - array with numbers of encoded points on curve. 89 | #print("c =", c); #show cypher-array 90 | 91 | # "array -> to string"; 92 | c_string = array_to_string(c, C, excluded_points_array, key, "point_", "hex_"); #show encoded string 93 | print("cyphertext: c_string =", c_string); #show cyphertext 94 | 95 | #back "string -> to array" ??? 96 | c_array = array_from_string(c_string, C, excluded_points_array, key, "point_", "hex_"); #decode array from cyphertext 97 | print("c == c_array", (c == c_array)); #show result of comparison arrays. 98 | 99 | decrypted_message = text_decode(c_array, C, excluded_points_array, n, key); #decoded message from array - text of message 100 | #decrypted_message = text_decode(c, C, excluded_points_array, n, key); #decoded message from encoded c_array - text of message 101 | print("open_message =", open_message, "\ndecrypted_message =", decrypted_message, "\ndecrypted_message == open_message??", decrypted_message==open_message); #compare open_message and decrypted_message 102 | 103 | 104 | 105 | print("_________________________________"); 106 | 107 | #______________________________________________________________________________________________________________________________ 108 | 109 | print("\n\ntest number encryption-decryption:\n"); 110 | #import random; #already imported. 111 | 112 | #open_message = random.randrange(0, C.char); #up to p-parameter of elliptic curve. 113 | open_message = random.randrange(0, math.pow(2,24)); #shorter number. 114 | #open_message = random.randrange(0, int(math.pow(2,512))); #message - number. Working. 115 | 116 | #print("open_message =", open_message); #show number 117 | c = int_encode(open_message, C, excluded_points_array, key); #get encrypted array - nubmers of encoded points on the elliptic curve. 118 | #print("c =", c); #show 119 | 120 | # "array -> to string"; 121 | c_string = array_to_string(c, C, excluded_points_array, key); #encode this array to string (optional parameters are default) 122 | print("cyphertext: c_string =", c_string); #show cyphertext string 123 | 124 | #back "string -> to array" ??? 125 | c_array = array_from_string(c_string, C, excluded_points_array, key, "h", "p"); #restore array from cyphertext string. 126 | print("c == c_array", (c == c_array)); #show result of comparison arrays. 127 | 128 | decrypted_message = int_decode(c_array, C, excluded_points_array, n, key); #decoded message from c_array - number 129 | #decrypted_message = int_decode(c, C, excluded_points_array, n, key); #decoded message from c - number 130 | print( 131 | "open_message =", open_message, 132 | "\ndecrypted_message =", decrypted_message, 133 | "\ndecrypted_message == open_message??", decrypted_message==open_message #compare m and decrypted_message 134 | ); 135 | 136 | print("_________________________________"); 137 | 138 | 139 | 140 | input("Press Enter to continue, and start 100 tests...") #pause and continue after press any key... 141 | #run test in cycle 142 | import time #to using interval 143 | print ("Start : %s" % time.ctime()) 144 | interval = 0.1; #seconds 145 | for m in range(0, 100): 146 | print ("continue : %s" % time.ctime()) 147 | time.sleep( interval ) # wait... 148 | #print("\n\n next test. Key:", key); 149 | #open_message = random.randrange(0, C.char); 150 | open_message = m; #use i as message. 151 | c = int_encode(open_message, C, excluded_points_array, key); #encoded and encrypted message - array with numbers of encoded points on curve 152 | decrypted_message = int_decode(c, C, excluded_points_array, n, key); #decoded and decrypted message - i 153 | if(decrypted_message!=open_message): 154 | print("\nError found! break;") 155 | print("open_message", open_message, "!=", "decrypted_message", decrypted_message, "open_message =", open_message, "decrypted_message =", decrypted_message, "key", key); 156 | 157 | print("len(excluded_points_array)", len(excluded_points_array)); 158 | for i in range(0, len(excluded_points_array)): 159 | is_on_curve = C.contains(excluded_points_array[i]); 160 | print("i = ", i, "excluded_points_array[i] =", excluded_points_array[i], "is_on_curve: ", is_on_curve); 161 | break; 162 | else: 163 | print("m", m, "(decrypted_message==open_message)", (decrypted_message==open_message)); 164 | 165 | if(m%C.char==0): 166 | excluded_points_array = generate_excluded_points(C); #regenerate array with excluded points. 167 | print ("End : %s" % time.ctime()) 168 | -------------------------------------------------------------------------------- /tests_mini_ecdsa.py: -------------------------------------------------------------------------------- 1 | #mini_ecdsa.py must to be in the same folder with this file. 2 | from mini_ecdsa import *; 3 | 4 | #tests the fucntions in mini_ecdsa.py 5 | def run_test(): 6 | print("_____________________") 7 | #smaller elliptic curve 8 | C = CurveOverFp(0, 0, 7, 211); 9 | P = Point(150, 22); 10 | print("\n\n", "smaller curve:", C, ", generator point: ", P); 11 | 12 | n = C.order(P) 13 | print("n", n); 14 | 15 | (d, Q) = generate_keypair(C, P, n); #limited value of d (max = n) 16 | print("d", d, "Q", Q); 17 | 18 | print("\ntest brute_force:"); 19 | crack_brute_force(C, P, n, Q, 150, 100); 20 | 21 | print("\ntest BSGS:"); 22 | crack_baby_giant(C, P, n, Q); 23 | 24 | print("\ntest pollard_rho:"); 25 | crack_rho(C, P, n, Q, 1); 26 | 27 | (d1, Q1) = generate_keypair(C, P, n); 28 | print("d1", d1, "Q1", Q1); 29 | 30 | print("\ntest subtraction:", Q.__eq__(C.add(C.subtract(Q, Q1),Q1))); 31 | print("\ntest divide:", Q.__eq__(C.mult(C.divide_point(Q, d1, n), d1))); 32 | 33 | print("test get_Y (even Y):", C.getY(Q1.x, 0)); 34 | print("test get_point_by_X (odd Y):", C.get_point_by_X(Q1.x, 1)); 35 | 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))); 36 | 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))); 37 | print("_____________________") 38 | #end function 39 | 40 | run_test(); #result of one test just as demo 41 | 42 | """ 43 | # test many random points with many iterations. 44 | import time #to using interval 45 | interval = 0.5; #seconds 46 | # run tests 47 | print ("Start : %s" % time.ctime()) 48 | for i in range(0, 100): 49 | print ("continue : %s" % time.ctime()) 50 | time.sleep( interval ) # wait... 51 | run_test(); # repeat test again, after some time 52 | print ("End : %s" % time.ctime()) 53 | """ --------------------------------------------------------------------------------