OLD | NEW |
(Empty) | |
| 1 # --- ELLIPTIC CURVE MATH ------------------------------------------------------ |
| 2 # |
| 3 # curve definition: y^2 = x^3 - p*x - q |
| 4 # over finite field: Z/nZ* (prime residue classes modulo a prime number n) |
| 5 # |
| 6 # |
| 7 # COPYRIGHT (c) 2010 by Toni Mattis <solaris@live.de> |
| 8 # ------------------------------------------------------------------------------ |
| 9 |
| 10 """ |
| 11 Module for elliptic curve arithmetic over a prime field GF(n). |
| 12 E(GF(n)) takes the form y**2 == x**3 - p*x - q (mod n) for a prime n. |
| 13 |
| 14 0. Structures used by this module |
| 15 |
| 16 PARAMETERS and SCALARS are non-negative (long) integers. |
| 17 |
| 18 A POINT (x, y), usually denoted p1, p2, ... |
| 19 is a pair of (long) integers where 0 <= x < n and 0 <= y < n |
| 20 |
| 21 A POINT in PROJECTIVE COORDINATES, usually denoted jp1, jp2, ... |
| 22 takes the form (X, Y, Z, Z**2, Z**3) where x = X / Z**2 |
| 23 and y = Y / z**3. This form is called Jacobian coordinates. |
| 24 |
| 25 The NEUTRAL element "0" or "O" is represented by None |
| 26 in both coordinate systems. |
| 27 |
| 28 1. Basic Functions |
| 29 |
| 30 euclid() Is the Extended Euclidean Algorithm. |
| 31 inv() Computes the multiplicative inversion modulo n. |
| 32 curve_q() Finds the curve parameter q (mod n) |
| 33 when p and a point are given. |
| 34 element() Tests whether a point (x, y) is on the curve. |
| 35 |
| 36 2. Point transformations |
| 37 |
| 38 to_projective() Converts a point (x, y) to projective coordinates. |
| 39 from_projective() Converts a point from projective coordinates |
| 40 to (x, y) using the transformation described above. |
| 41 neg() Computes the inverse point -P in both coordinate |
| 42 systems. |
| 43 |
| 44 3. Slow point arithmetic |
| 45 |
| 46 These algorithms make use of basic geometry and modular arithmetic |
| 47 thus being suitable for small numbers and academic study. |
| 48 |
| 49 add() Computes the sum of two (x, y)-points |
| 50 mul() Perform scalar multiplication using "double & add" |
| 51 |
| 52 4. Fast point arithmetic |
| 53 |
| 54 These algorithms make use of projective coordinates, signed binary |
| 55 expansion and a JSP-like approach (joint sparse form). |
| 56 |
| 57 The following functions consume and return projective coordinates: |
| 58 |
| 59 addf() Optimized point addition. |
| 60 doublef() Optimized point doubling. |
| 61 mulf() Highly optimized scalar multiplication. |
| 62 muladdf() Highly optimized addition of two products. |
| 63 |
| 64 The following functions use the optimized ones above but consume |
| 65 and output (x, y)-coordinates for a more convenient usage: |
| 66 |
| 67 mulp() Encapsulates mulf() |
| 68 muladdp() Encapsulates muladdf() |
| 69 |
| 70 For single additions add() is generally faster than an encapsulation of |
| 71 addf() which would involve expensive coordinate transformations. |
| 72 Hence there is no addp() and doublep(). |
| 73 """ |
| 74 from __future__ import division |
| 75 try: |
| 76 from builtins import zip |
| 77 from builtins import range |
| 78 except ImportError: |
| 79 pass |
| 80 #from past.utils import old_div |
| 81 |
| 82 # BASIC MATH ------------------------------------------------------------------- |
| 83 |
| 84 |
| 85 def euclid(a, b): |
| 86 """Solve x*a + y*b = ggt(a, b) and return (x, y, ggt(a, b))""" |
| 87 # Non-recursive approach hence suitable for large numbers |
| 88 x = yy = 0 |
| 89 y = xx = 1 |
| 90 while b: |
| 91 q = a // b |
| 92 a, b = b, a % b |
| 93 x, xx = xx - q * x, x |
| 94 y, yy = yy - q * y, y |
| 95 return xx, yy, a |
| 96 |
| 97 |
| 98 def inv(a, n): |
| 99 """Perform inversion 1/a modulo n. a and n should be COPRIME.""" |
| 100 # coprimality is not checked here in favour of performance |
| 101 i = euclid(a, n)[0] |
| 102 while i < 0: |
| 103 i += n |
| 104 return i |
| 105 |
| 106 |
| 107 def curve_q(x, y, p, n): |
| 108 """Find curve parameter q mod n having point (x, y) and parameter p""" |
| 109 return ((x * x - p) * x - y * y) % n |
| 110 |
| 111 |
| 112 def element(point, p, q, n): |
| 113 """Test, whether the given point is on the curve (p, q, n)""" |
| 114 if point: |
| 115 x, y = point |
| 116 return (x * x * x - p * x - q) % n == (y * y) % n |
| 117 else: |
| 118 return True |
| 119 |
| 120 |
| 121 def to_projective(p): |
| 122 """Transform point p given as (x, y) to projective coordinates""" |
| 123 if p: |
| 124 return (p[0], p[1], 1, 1, 1) |
| 125 else: |
| 126 return None # Identity point (0) |
| 127 |
| 128 |
| 129 def from_projective(jp, n): |
| 130 """Transform a point from projective coordinates to (x, y) mod n""" |
| 131 if jp: |
| 132 return (jp[0] * inv(jp[3], n)) % n, (jp[1] * inv(jp[4], n)) % n |
| 133 else: |
| 134 return None # Identity point (0) |
| 135 |
| 136 |
| 137 def neg(p, n): |
| 138 """Compute the inverse point to p in any coordinate system""" |
| 139 return (p[0], (n - p[1]) % n) + p[2:] if p else None |
| 140 |
| 141 |
| 142 # POINT ADDITION --------------------------------------------------------------- |
| 143 |
| 144 # addition of points in y**2 = x**3 - p*x - q over <Z/nZ*; +> |
| 145 def add(p, q, n, p1, p2): |
| 146 """Add points p1 and p2 over curve (p, q, n)""" |
| 147 if p1 and p2: |
| 148 x1, y1 = p1 |
| 149 x2, y2 = p2 |
| 150 if (x1 - x2) % n: |
| 151 s = ((y1 - y2) * inv(x1 - x2, n)) % n # slope |
| 152 x = (s * s - x1 - x2) % n # intersection with curve |
| 153 return x, n - (y1 + s * (x - x1)) % n |
| 154 else: |
| 155 if (y1 + y2) % n: # slope s calculated by derivation |
| 156 s = ((3 * x1 * x1 - p) * inv(2 * y1, n)) % n |
| 157 x = (s * s - 2 * x1) % n # intersection with curve |
| 158 return x, n - (y1 + s * (x - x1)) % n |
| 159 else: |
| 160 return None |
| 161 else: # either p1 is not none -> ret. p1, otherwiese p2, which may be |
| 162 return p1 if p1 else p2 # none too. |
| 163 |
| 164 |
| 165 # faster addition: redundancy in projective coordinates eliminates |
| 166 # expensive inversions mod n. |
| 167 def addf(p, q, n, jp1, jp2): |
| 168 """Add jp1 and jp2 in projective (jacobian) coordinates.""" |
| 169 if jp1 and jp2: |
| 170 |
| 171 x1, y1, z1, z1s, z1c = jp1 |
| 172 x2, y2, z2, z2s, z2c = jp2 |
| 173 |
| 174 s1 = (y1 * z2c) % n |
| 175 s2 = (y2 * z1c) % n |
| 176 |
| 177 u1 = (x1 * z2s) % n |
| 178 u2 = (x2 * z1s) % n |
| 179 |
| 180 if (u1 - u2) % n: |
| 181 |
| 182 h = (u2 - u1) % n |
| 183 r = (s2 - s1) % n |
| 184 |
| 185 hs = (h * h) % n |
| 186 hc = (hs * h) % n |
| 187 |
| 188 x3 = (-hc - 2 * u1 * hs + r * r) % n |
| 189 y3 = (-s1 * hc + r * (u1 * hs - x3)) % n |
| 190 z3 = (z1 * z2 * h) % n |
| 191 |
| 192 z3s = (z3 * z3) % n |
| 193 z3c = (z3s * z3) % n |
| 194 |
| 195 return x3, y3, z3, z3s, z3c |
| 196 |
| 197 else: |
| 198 if (s1 + s2) % n: |
| 199 return doublef(p, q, n, jp1) |
| 200 else: |
| 201 return None |
| 202 else: |
| 203 return jp1 if jp1 else jp2 |
| 204 |
| 205 # explicit point doubling using redundant coordinates |
| 206 def doublef(p, q, n, jp): |
| 207 """Double jp in projective (jacobian) coordinates""" |
| 208 if not jp: |
| 209 return None |
| 210 x1, y1, z1, z1p2, z1p3 = jp |
| 211 |
| 212 y1p2 = (y1 * y1) % n |
| 213 a = (4 * x1 * y1p2) % n |
| 214 b = (3 * x1 * x1 - p * z1p3 * z1) % n |
| 215 x3 = (b * b - 2 * a) % n |
| 216 y3 = (b * (a - x3) - 8 * y1p2 * y1p2) % n |
| 217 z3 = (2 * y1 * z1) % n |
| 218 z3p2 = (z3 * z3) % n |
| 219 |
| 220 return x3, y3, z3, z3p2, (z3p2 * z3) % n |
| 221 |
| 222 |
| 223 # SCALAR MULTIPLICATION -------------------------------------------------------- |
| 224 |
| 225 # scalar multiplication p1 * c = p1 + p1 + ... + p1 (c times) in O(log(n)) |
| 226 def mul(p, q, n, p1, c): |
| 227 """multiply point p1 by scalar c over curve (p, q, n)""" |
| 228 res = None |
| 229 while c > 0: |
| 230 if c & 1: |
| 231 res = add(p, q, n, res, p1) |
| 232 c >>= 1 # c = c / 2 |
| 233 p1 = add(p, q, n, p1, p1) # p1 = p1 * 2 |
| 234 return res |
| 235 |
| 236 |
| 237 # this method allows _signed_bin() to choose between 1 and -1. It will select |
| 238 # the sign which leaves the higher number of zeroes in the binary |
| 239 # representation (the higher GDB). |
| 240 def _gbd(n): |
| 241 """Compute second greatest base-2 divisor""" |
| 242 i = 1 |
| 243 if n <= 0: return 0 |
| 244 while not n % i: |
| 245 i <<= 1 |
| 246 return i >> 2 |
| 247 |
| 248 |
| 249 # This method transforms n into a binary representation having signed bits. |
| 250 # A signed binary expansion contains more zero-bits hence reducing the number |
| 251 # of additions required by a multiplication algorithm. |
| 252 # |
| 253 # Example: 15 ( 0b1111 ) can be written as 16 - 1, resulting in (1,0,0,0,-1) |
| 254 # and saving 2 additions. Subtraction can be performed as |
| 255 # efficiently as addition. |
| 256 def _signed_bin(n): |
| 257 """Transform n into an optimized signed binary representation""" |
| 258 r = [] |
| 259 while n > 1: |
| 260 if n & 1: |
| 261 cp = _gbd(n + 1) |
| 262 cn = _gbd(n - 1) |
| 263 if cp > cn: # -1 leaves more zeroes -> subtract -1 (= +1) |
| 264 r.append(-1) |
| 265 n += 1 |
| 266 else: # +1 leaves more zeroes -> subtract +1 (= -1) |
| 267 r.append(+1) |
| 268 n -= 1 |
| 269 else: |
| 270 r.append(0) # be glad about one more zero |
| 271 n >>= 1 |
| 272 r.append(n) |
| 273 return r[::-1] |
| 274 |
| 275 |
| 276 # This multiplication algorithm combines signed binary expansion and |
| 277 # fast addition using projective coordinates resulting in 5 to 10 times |
| 278 # faster multiplication. |
| 279 def mulf(p, q, n, jp1, c): |
| 280 """Multiply point jp1 by c in projective coordinates""" |
| 281 sb = _signed_bin(c) |
| 282 res = None |
| 283 jp0 = neg(jp1, n) # additive inverse of jp1 to be used fot bit -1 |
| 284 for s in sb: |
| 285 res = doublef(p, q, n, res) |
| 286 if s: |
| 287 res = addf(p, q, n, res, jp1) if s > 0 else \ |
| 288 addf(p, q, n, res, jp0) |
| 289 return res |
| 290 |
| 291 |
| 292 # Encapsulates mulf() in order to enable flat coordinates (x, y) |
| 293 def mulp(p, q, n, p1, c): |
| 294 """Multiply point p by c using fast multiplication""" |
| 295 return from_projective(mulf(p, q, n, to_projective(p1), c), n) |
| 296 |
| 297 |
| 298 # Sum of two products using Shamir's trick and signed binary expansion |
| 299 def muladdf(p, q, n, jp1, c1, jp2, c2): |
| 300 """Efficiently compute c1 * jp1 + c2 * jp2 in projective coordinates""" |
| 301 s1 = _signed_bin(c1) |
| 302 s2 = _signed_bin(c2) |
| 303 diff = len(s2) - len(s1) |
| 304 if diff > 0: |
| 305 s1 = [0] * diff + s1 |
| 306 elif diff < 0: |
| 307 s2 = [0] * -diff + s2 |
| 308 |
| 309 jp1p2 = addf(p, q, n, jp1, jp2) |
| 310 jp1n2 = addf(p, q, n, jp1, neg(jp2, n)) |
| 311 |
| 312 precomp = ((None, jp2, neg(jp2, n)), |
| 313 (jp1, jp1p2, jp1n2), |
| 314 (neg(jp1, n), neg(jp1n2, n), neg(jp1p2, n))) |
| 315 res = None |
| 316 |
| 317 for i, j in zip(s1, s2): |
| 318 res = doublef(p, q, n, res) |
| 319 if i or j: |
| 320 res = addf(p, q, n, res, precomp[i][j]) |
| 321 return res |
| 322 |
| 323 |
| 324 # Encapsulate muladdf() |
| 325 def muladdp(p, q, n, p1, c1, p2, c2): |
| 326 """Efficiently compute c1 * p1 + c2 * p2 in (x, y)-coordinates""" |
| 327 return from_projective(muladdf(p, q, n, |
| 328 to_projective(p1), c1, |
| 329 to_projective(p2), c2), n) |
| 330 |
| 331 # POINT COMPRESSION ------------------------------------------------------------ |
| 332 |
| 333 # Compute the square root modulo n |
| 334 |
| 335 |
| 336 # Determine the sign-bit of a point allowing to reconstruct y-coordinates |
| 337 # when x and the sign-bit are given: |
| 338 def sign_bit(p1): |
| 339 """Return the signedness of a point p1""" |
| 340 return p1[1] % 2 if p1 else 0 |
| 341 |
| 342 # Reconstruct the y-coordinate when curve parameters, x and the sign-bit of |
| 343 # the y coordinate are given: |
| 344 def y_from_x(x, p, q, n, sign): |
| 345 """Return the y coordinate over curve (p, q, n) for given (x, sign)""" |
| 346 |
| 347 # optimized form of (x**3 - p*x - q) % n |
| 348 a = (((x * x) % n - p) * x - q) % n |
| 349 |
| 350 |
| 351 if __name__ == "__main__": |
| 352 import rsa |
| 353 import time |
| 354 |
| 355 t = time.time() |
| 356 n = rsa.get_prime(256 / 8, 20) |
| 357 tp = time.time() - t |
| 358 p = rsa.random.randint(1, n) |
| 359 p1 = (rsa.random.randint(1, n), rsa.random.randint(1, n)) |
| 360 q = curve_q(p1[0], p1[1], p, n) |
| 361 r1 = rsa.random.randint(1, n) |
| 362 r2 = rsa.random.randint(1, n) |
| 363 q1 = mulp(p, q, n, p1, r1) |
| 364 q2 = mulp(p, q, n, p1, r2) |
| 365 s1 = mulp(p, q, n, q1, r2) |
| 366 s2 = mulp(p, q, n, q2, r1) |
| 367 s1 == s2 |
| 368 tt = time.time() - t |
| 369 |
| 370 def test(tcount, bits=256): |
| 371 n = rsa.get_prime(bits / 8, 20) |
| 372 p = rsa.random.randint(1, n) |
| 373 p1 = (rsa.random.randint(1, n), rsa.random.randint(1, n)) |
| 374 q = curve_q(p1[0], p1[1], p, n) |
| 375 p2 = mulp(p, q, n, p1, rsa.random.randint(1, n)) |
| 376 |
| 377 c1 = [rsa.random.randint(1, n) for i in range(tcount)] |
| 378 c2 = [rsa.random.randint(1, n) for i in range(tcount)] |
| 379 c = list(zip(c1, c2)) |
| 380 |
| 381 t = time.time() |
| 382 for i, j in c: |
| 383 from_projective(addf(p, q, n, |
| 384 mulf(p, q, n, to_projective(p1), i), |
| 385 mulf(p, q, n, to_projective(p2), j)), n) |
| 386 t1 = time.time() - t |
| 387 t = time.time() |
| 388 for i, j in c: |
| 389 muladdp(p, q, n, p1, i, p2, j) |
| 390 t2 = time.time() - t |
| 391 |
| 392 return tcount, t1, t2 |
| 393 |
| 394 |
| 395 |
OLD | NEW |