kryptools 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
kryptools/dlp_rho.py ADDED
@@ -0,0 +1,62 @@
1
+ """
2
+ Discrete log solvers: Pollard rho
3
+ """
4
+
5
+ from math import gcd, isqrt
6
+ from random import randint
7
+
8
+
9
+ def dlog_rho(a: int, b: int, n: int, m: int = None, brent=0) -> int:
10
+ """Compute the discrete log_a(b) in Z_p of an element a of order m using Pollard's rho algorithm."""
11
+ a %= n
12
+ b %= n
13
+ if not m:
14
+ m = n - 1
15
+ trys = 10
16
+ max_iter = 10 * isqrt(m) # stop iterating and try a different start value
17
+
18
+ def f(x: int, alpha: int, beta: int) -> (int, int, int):
19
+ r = x % 3
20
+ if r == 0:
21
+ return x * a % n, (alpha + 1) % m, beta
22
+ if r == 1:
23
+ return pow(x, 2, n), (2 * alpha) % m, (2 * beta) % m
24
+ return (x * b) % n, alpha, (beta + 1) % m
25
+
26
+ while trys > 0:
27
+ j0 = randint(1, m - 1)
28
+ x, alpha_x, beta_x = (b * pow(a, j0, n)) % n, j0, 1 # x_0
29
+ if brent == 0: # Floyd's cycle detection algorithm
30
+ i = 1
31
+ x, alpha_x, beta_x = f(x, alpha_x, beta_x) # x_1=f(x_0)
32
+ y, alpha_y, beta_y = f(x, alpha_x, beta_x) # y_1=f(f(x_0))
33
+ while x != y and i < max_iter:
34
+ i += 1
35
+ x, alpha_x, beta_x = f(x, alpha_x, beta_x) # f(x_j)
36
+ y, alpha_y, beta_y = f(y, alpha_y, beta_y)
37
+ y, alpha_y, beta_y = f(y, alpha_y, beta_y) # f(f(y_j))
38
+ else: # Brent's cycle detection algorithm
39
+ i = 1 # search for a cycle length k < 2^i
40
+ k = 1 # cycle length
41
+ y, alpha_y, beta_y = f(x, alpha_x, beta_x) # f(x_0)
42
+ while x != y and i < max_iter:
43
+ if i == k: # start a new power of two
44
+ x, alpha_x, beta_x = y, alpha_y, beta_y
45
+ i *= 2
46
+ k = 0
47
+ y, alpha_y, beta_y = f(y, alpha_y, beta_y)
48
+ k += 1
49
+ # we found a collission (or hit max_iter)
50
+ if x == y and (beta_y - beta_x) % m != 0:
51
+ d = gcd(beta_y - beta_x, m)
52
+ mm = m // d
53
+ if (alpha_x - alpha_y) % d != 0:
54
+ return None # no solution
55
+ l = (((alpha_x - alpha_y) // d) * pow((beta_y - beta_x) // d, -1, mm)) % mm
56
+ while pow(a, l, n) != b:
57
+ if l >= m:
58
+ return None
59
+ l += mm
60
+ return l
61
+ trys -= 1
62
+ raise Exception("Sorry, pollard rho failed!")
kryptools/ec.py ADDED
@@ -0,0 +1,383 @@
1
+ """
2
+ Elliptic curves
3
+ """
4
+
5
+ from math import isqrt, floor, sqrt
6
+ from random import randint
7
+ from .factor import factorint
8
+ from .nt import legendre_symbol, sqrt_mod, crt
9
+ from .Zmod import Zmod
10
+
11
+ class EC_Weierstrass():
12
+ """
13
+ Elliptic curve in Weierstrass normal form y^2 = x^3 + ax + b mod p.
14
+
15
+ Example:
16
+
17
+ To define an elliptic curve use
18
+ >>> ec = EC_Weierstrass(239, 3, 1)
19
+
20
+ To declare a point on the elliptic curve use
21
+ >>> P = ec(43, 222)
22
+ >>> Q = ec(148, 218))
23
+
24
+
25
+ The usual arithmetic operations are supported.
26
+ >>> P + Q
27
+ (195, 41)
28
+ """
29
+
30
+ def __init__(self, p: int, a: int, b: int, order: int = None):
31
+ if p < 3:
32
+ raise ValueError(f"{p} must be a prime larger than 2.")
33
+ if (4 * pow(a, 3, p) + 27 * pow(b, 2, p) ) % p == 0:
34
+ raise ValueError(f"Curve is singular!")
35
+ self.p = p
36
+ self.gf = Zmod(p, short = True)
37
+ self.a = self.gf(a % p)
38
+ self.b = self.gf(b % p)
39
+ self.group_order = order
40
+ self.group_order_factors = None
41
+ self.short = False # display points in short format
42
+ self.hex = False # display points as hex values in compressed format
43
+
44
+ def __call__(self, x: int | str | None = None, y: int | None = None, short: bool = False):
45
+ if isinstance(x, str):
46
+ x = x.replace(" ", "")
47
+ type = x[:2]
48
+ x = x[2:]
49
+ if type == '02' or type == '03':
50
+ short = 1
51
+ x = int(x, 16)
52
+ if type == '02':
53
+ y = 0
54
+ else:
55
+ y = 1
56
+ elif type == '04':
57
+ l = len(x)//2
58
+ y = int(x[l:], 16)
59
+ x = int(x[:l], 16)
60
+ else:
61
+ raise ValueError('Unsupported hex type.')
62
+ return ECPoint(x, y, self, short)
63
+
64
+ def __eq__(self, other):
65
+ if isinstance(other, self.__class__):
66
+ return self.p == other.p and self.a == other.a and self.b == other.b
67
+ return False
68
+
69
+ def __contains__(self, P: "ECPoint") -> bool:
70
+ return P.y**2 == P.x**3 + self.a * P.x + self.b
71
+
72
+ def info(self):
73
+ format = "Weierstrass curve y^2 = x^3"
74
+ if int(self.a):
75
+ format += f" + {self.a} x"
76
+ if int(self.b):
77
+ format += f" + {self.b}"
78
+ format += f" over Z_{self.p}."
79
+ print(format)
80
+
81
+ def add(self, x1, y1, x2, y2):
82
+ if x1 is None:
83
+ return x2, y2
84
+ if x2 is None:
85
+ return x1, y1
86
+ if x1 == x2:
87
+ if y1 == y2:
88
+ return self.dbl(x1, y1)
89
+ return None, None
90
+ s = (y2 - y1) / (x2 - x1)
91
+ x3 = s**2 - x1 - x2
92
+ y3 = s * (x1 - x3) - y1
93
+ return x3, y3
94
+
95
+ def dbl(self, x, y):
96
+ if x is None or not y:
97
+ return None, None
98
+ s = (3 * x**2 + self.a) / (2 * y)
99
+ x3 = s**2 - x - x
100
+ y3 = s * (x - x3) - y
101
+ return x3, y3
102
+
103
+ def mult(self, j: int, x, y): # Addition-subtraction ladder
104
+ if j == 0:
105
+ return None, None
106
+ if j < 0:
107
+ y = -y
108
+ j *= -1
109
+ # xx, yy = None, None
110
+ # while j > 0:
111
+ # # If j is odd, add x
112
+ # if j & 1:
113
+ # xx, yy = self.add(xx, yy, x, y)
114
+ # # Now double
115
+ # j >>= 1 # j= j//2
116
+ # x, y = self.dbl(x, y)
117
+ xx, yy = x, y
118
+ j3 = 3 * j
119
+ form = "0" + str(j3.bit_length()) + "b"
120
+ for a, b in zip(format(j3, form)[1:-1], format(j, form)[1:-1]):
121
+ xx, yy = self.dbl(xx, yy)
122
+ if a == "1" and b == "0":
123
+ xx, yy = self.add(xx, yy, x, y)
124
+ if a == "0" and b == "1":
125
+ xx, yy = self.add(xx, yy, x, -y)
126
+ return xx, yy
127
+
128
+ def random(self):
129
+ j = -1
130
+ while j == -1:
131
+ x = self.gf(randint(0, self.p - 1))
132
+ y2 = int(x**3 + self.a * x + self.b)
133
+ j = legendre_symbol(y2, self.p)
134
+ return ECPoint(x, randint(0, 1), self, short = True)
135
+
136
+ def order(self, order: int = None) -> int:
137
+ if order:
138
+ self.group_order = order
139
+ elif not self.group_order:
140
+ if self.p < 230:
141
+ self.group_order = self.order_naive()
142
+ else:
143
+ self.group_order = self.order_shanks_mestre()
144
+ return self.group_order
145
+
146
+ def factor_order(self) -> dict:
147
+ if self.group_order_factors:
148
+ return self.group_order_factors
149
+ if not self.group_order:
150
+ self.order()
151
+ self.group_order_factors = factorint(self.group_order)
152
+ return self.group_order_factors
153
+
154
+ def order_naive(self) -> int:
155
+ a1 = (int(self.a) + 1) % self.p
156
+ y = int(self.b)
157
+ order = self.p + 1 + legendre_symbol(y, self.p)
158
+ for x in range(self.p - 1):
159
+ y += (3 * x * (x + 1) + a1) % self.p
160
+ order += legendre_symbol(y, self.p)
161
+ return order
162
+
163
+ def order_shanks_mestre(self) -> int:
164
+ if self.p < 230:
165
+ return self.order_naive()
166
+ j = 1
167
+ while j != -1:
168
+ g = randint(1, self.p - 1)
169
+ j = legendre_symbol(g, self.p)
170
+ W = floor(sqrt(2 * sqrt(self.p)))
171
+ a, b = int(self.a), int(self.b)
172
+ while True:
173
+ sigma = 0
174
+ while sigma == 0:
175
+ x = randint(0, self.p - 1)
176
+ sigma = legendre_symbol(x**3 + a * x + b, self.p)
177
+ if sigma == 1:
178
+ ec = EC_Weierstrass(self.p, a, b)
179
+ else:
180
+ ec = EC_Weierstrass(
181
+ self.p, pow(g, 2, self.p) * a, pow(g, 3, self.p) * b
182
+ )
183
+ x = g * x % self.p
184
+ x = ec.gf(x)
185
+ y2 = int(x**3 + ec.a * x + ec.b)
186
+ y = ec.gf(sqrt_mod(y2, self.p))
187
+
188
+ A = {}
189
+ xx, yy = ec.mult(ec.p + 1, x, y)
190
+ for j in range(W):
191
+ if xx is None:
192
+ A[None] = j
193
+ else:
194
+ A[int(xx)] = j
195
+ xx, yy = ec.add(xx, yy, x, y)
196
+ B = []
197
+ xg, yg = ec.mult(W, x, y)
198
+ xx, yy = None, None
199
+ for j in range(W + 1):
200
+ if xx is None:
201
+ if xx in A:
202
+ B.append([A[None], j])
203
+ elif int(xx) in A:
204
+ B.append([A[int(xx)], j])
205
+ xx, yy = ec.add(xx, yy, xg, yg)
206
+ if len(B) == 1:
207
+ beta, gamma = B[0]
208
+ break
209
+ t = beta + gamma * W
210
+ if ec.mult(ec.p + 1 + t, x, y)[0] is not None:
211
+ t = beta - gamma * W
212
+ assert ec.mult(ec.p + 1 + t, x, y)[0] is None
213
+
214
+ return self.p + 1 + sigma * t
215
+
216
+ class ECPoint:
217
+ "Point on an elliptic curve"
218
+ def __init__(self, x: int, y: int, curve: EC_Weierstrass, short:bool = False):
219
+ self.curve = curve
220
+ if x is None:
221
+ self.x = None
222
+ self.y = None
223
+ else:
224
+ self.x = curve.gf(x)
225
+ if short:
226
+ y2 = int(self.x**3 + curve.a * self.x + curve.b)
227
+ j = legendre_symbol(y2, curve.p)
228
+ if j == -1:
229
+ raise ValueError("Point not on curve!")
230
+ y1 = sqrt_mod(y2, curve.p)
231
+ if y % 2 == 0:
232
+ y = y1
233
+ else:
234
+ y = (curve.p - y1) % curve.p
235
+ self.y = curve.gf(y)
236
+ if not self in curve:
237
+ raise ValueError("Point not on curve!")
238
+
239
+ def __repr__(self):
240
+ if self.x is None:
241
+ return "O"
242
+ if self.curve.hex is True:
243
+ if self.curve.short is True:
244
+ pre = '0x02'
245
+ if int(self.y) % 2 == 1:
246
+ pre = '0x03'
247
+ return pre+f'{int(self.x):x}'
248
+ pre = '0x04'
249
+ return pre+f'{int(self.x):x}{int(self.y):x}'
250
+ if self.curve.short is True:
251
+ return f"({int(self.x)}, {int(self.y) % 2})"
252
+ return f"({int(self.x)}, {int(self.y)})"
253
+
254
+ def __eq__(self, other):
255
+ if not isinstance(other, self.__class__):
256
+ return False
257
+ if not self.curve == other.curve:
258
+ return False
259
+ if self.x is None:
260
+ return other.x is None
261
+ if other.x is None:
262
+ return self.x is None
263
+ return self.x == other.x and self.y == other.y
264
+
265
+ def __bool__(self):
266
+ return self.x is not None
267
+
268
+ def __hash__(self):
269
+ if self.x is None:
270
+ return hash(None)
271
+ return hash((int(self.x), int(self.y)))
272
+
273
+ def __add__(self, other: "ECPoint") -> "ECPoint":
274
+ if not isinstance(other, self.__class__) or self.curve != other.curve:
275
+ raise ValueError(f"Cannot add {self} and {other}.")
276
+ x, y = self.curve.add(self.x, self.y, other.x, other.y)
277
+ return ECPoint(x, y, self.curve)
278
+
279
+ def __sub__(self, other: "ECPoint") -> "ECPoint":
280
+ return self + other.__neg__()
281
+
282
+ def __rmul__(self, scalar: int) -> "ECPoint":
283
+ x, y = self.curve.mult(scalar, self.x, self.y)
284
+ return ECPoint(x, y, self.curve)
285
+
286
+ def __neg__(self) -> "ECPoint":
287
+ if self.x is None or not self.y:
288
+ return self
289
+ return ECPoint(self.x, -self.y, self.curve)
290
+
291
+ def order(self) -> int:
292
+ """Compute the order of an element."""
293
+ self.curve.factor_order() # Make sure the factorization is available
294
+ order = self.curve.group_order
295
+ for p, k in self.curve.group_order_factors.items():
296
+ for _ in range(k):
297
+ order_try = order // p
298
+ if self.curve.mult(order_try, self.x, self.y)[0] is None:
299
+ order = order_try
300
+ else:
301
+ break
302
+ return order
303
+
304
+ def dlog(Q, P: "ECPoint") -> int:
305
+ """Compute the discrete log_P(Q) in EC."""
306
+ m = P.order()
307
+ mf = factorint(m)
308
+ assert m * Q == P.curve(None, None), "DLP not solvable."
309
+ # We first use Pohlig-Hellman to split m into powers of prime factors
310
+ mm = []
311
+ ll = []
312
+ for pj, kj in mf.items():
313
+ Pj = (m // pj**kj) * P
314
+ Qj = (m // pj**kj) * Q
315
+ l = Qj.dlog_ph(Pj, pj, kj)
316
+ if l is None:
317
+ return None
318
+ mm += [pj**kj]
319
+ ll += [l]
320
+ return crt(ll, mm)
321
+
322
+ def dlog_ph(Q, P: "ECPoint", q: int, k: int) -> int:
323
+ """Compute the discrete log_P(Q) in EC if P has order q^k using Pohlig-Hellman reduction."""
324
+ if k == 1 or q**k < 10000:
325
+ return Q.dlog_switch(P, q**k)
326
+ Pj = q**(k - 1) * P
327
+ P1 = Pj
328
+ Qj = q**(k - 1) * Q
329
+ xj = Qj.dlog_switch(P1, q)
330
+ for j in range(2, k + 1):
331
+ Pj = q**(k - j) * P
332
+ Qj = q**(k - j) * Q - xj * Pj
333
+ yj = Qj.dlog_switch(P1, q)
334
+ xj = xj + q ** (j - 1) * yj % q**j
335
+ return xj
336
+
337
+ def dlog_switch(Q, P: "ECPoint", m: int) -> int:
338
+ """Compute the discrete log_P(Q) in EC if P has order m choosing an appropriate method."""
339
+ if m < 100:
340
+ return Q.dlog_naive(P, m)
341
+ return Q.dlog_bsgs(P, m)
342
+
343
+ def dlog_naive(Q, P: "ECPoint", m: int) -> int:
344
+ """Compute the discrete log_P(Q) in EC using an exhaustive search."""
345
+ if not Q.curve == P.curve and not isinstance(Q, P.__class__):
346
+ raise ValueError(f"Points must be on the same curve!")
347
+ j = 0
348
+ xx, yy = None, None
349
+ while xx != Q.x:
350
+ j += 1
351
+ xx, yy = P.curve.add(xx, yy, P.x, P.y)
352
+ if xx is None:
353
+ raise ValueError("DLP not solvabel!")
354
+ if yy == Q.y:
355
+ return j
356
+ return m - j
357
+
358
+ def dlog_bsgs(Q, P: "ECPoint", m: int) -> int:
359
+ """Compute the discrete log_P(Q) in EC if P has order m using Shanks' baby-step-giant-step algorithm."""
360
+ if not Q.curve == P.curve and not isinstance(P, Q.__class__):
361
+ raise ValueError(f"Points must be on the same curve!")
362
+ mm = 1 + isqrt(m - 1)
363
+ m2 = mm//2 + mm % 1 # we use the group symmetry to halve the number of steps
364
+ # initialize baby_steps table
365
+ baby_steps = {}
366
+ baby_step = P
367
+ for j in range(1,m2+1):
368
+ baby_steps[int(baby_step.x)] = j, int(baby_step.y)
369
+ baby_step += P
370
+
371
+ # now take the giant steps
372
+ giant_stride = -mm * P
373
+ giant_step = Q
374
+ for l in range(mm+1):
375
+ if giant_step.x == None:
376
+ return l * mm
377
+ if int(giant_step.x) in baby_steps:
378
+ j = baby_steps[int(giant_step.x)][0]
379
+ if int(giant_step.y) != baby_steps[int(giant_step.x)][1]:
380
+ j *= -1
381
+ return (l * mm + j) % m
382
+ giant_step += giant_stride
383
+ raise ValueError("DLP not solvabel!")
kryptools/factor.py ADDED
@@ -0,0 +1,138 @@
1
+ """
2
+ Factorization of integers:
3
+ factorint(n) factorize the integer n into prime factors
4
+ """
5
+
6
+ from math import isqrt, gcd
7
+ from .primes import sieve_eratosthenes, isprime
8
+
9
+
10
+ # Factoring
11
+
12
+
13
+
14
+ def _factor_fermat(n: int, steps: int = 10) -> list:
15
+ a = isqrt(n - 1) + 1
16
+ step =2
17
+ if n % 3 == 2: # if n % 3 = 2, then a must be a multiple of 3
18
+ a += 2 - ((a - 1) % 3)
19
+ step = 3
20
+ elif (n % 4 == 1) ^ (a & 1): # if n % 4 = 1,3 then a must be odd, even, respectively
21
+ a += 1
22
+ for _ in range(steps):
23
+ #if a > (n + 9) // 6:
24
+ # return
25
+ b = isqrt(a * a - n)
26
+ if b * b == a * a - n:
27
+ return a - b
28
+ a += step
29
+
30
+ from .factor_pm1 import _pm1_parameters, factor_pm1
31
+ from .factor_ecm import _ecm_parameters, factor_ecm
32
+ #from .factor_qs import factor_qs
33
+
34
+ def factorint(n: int, verbose: int = 0) -> list:
35
+ "Factor a number."
36
+ prime_factors = {}
37
+
38
+ def add_factors(m: int, mm: tuple) -> None:
39
+ k = remaining_factors[m]
40
+ del remaining_factors[m]
41
+
42
+ for m in mm:
43
+ if m in prime_factors:
44
+ prime_factors[m] += k
45
+ elif isprime(m):
46
+ prime_factors[m] = k
47
+ else:
48
+ if m in remaining_factors:
49
+ remaining_factors[m] += k
50
+ elif m in new_factors:
51
+ new_factors[m] += k
52
+ else:
53
+ new_factors[m] = k
54
+
55
+ # trial division
56
+ B = 2500
57
+ factorbase = sieve_eratosthenes(B)
58
+ for p in factorbase:
59
+ k = 0
60
+ while n % p == 0:
61
+ k += 1
62
+ n //= p
63
+ if k:
64
+ prime_factors[p] = k
65
+ if n == 1:
66
+ return prime_factors
67
+ if verbose:
68
+ print("Trial division found:", list(prime_factors))
69
+ if isprime(n):
70
+ prime_factors[n] = 1
71
+ return prime_factors
72
+ remaining_factors = { n: 1 }
73
+
74
+ # https://gitlab.inria.fr/zimmerma/ecm/
75
+ ECM_PARAMETERS = [
76
+ [ 11000, 1900000, 74],
77
+ [ 50000, 13000000, 221],
78
+ [ 250000, 130000000, 453],
79
+ [ 1000000, 1000000000, 984],
80
+ [ 3000000, 5700000000, 2541],
81
+ [ 11000000, 35000000000, 4949],
82
+ [ 43000000, 240000000000, 8266],
83
+ [110000000, 780000000000, 20158],
84
+ [260000000, 3200000000000, 47173],
85
+ [850000000, 16000000000000, 77666]
86
+ ]
87
+
88
+ for parameters in ECM_PARAMETERS:
89
+ B1, B2, num_curves = parameters
90
+ num_curves *= 2
91
+ D = isqrt(B2)
92
+ primes = sieve_eratosthenes(B1 - 1 + ((B2 - B1 + 1) // (2 * D) + 1) * 2 * D)
93
+ pm1_parameters = _pm1_parameters(10 * B1, B2, primes = primes)
94
+ ecm_parameters = tuple([num_curves] + list(_ecm_parameters(B1, B2, D, primes = primes)))
95
+
96
+ methods = {_factor_fermat: "fm", factor_pm1: "pm1", factor_ecm: "ecm"} #, factor_qs: "qs"}
97
+ while remaining_factors:
98
+ new_factors = {}
99
+ for method in [ _factor_fermat, factor_pm1, factor_ecm ]: # , factor_qs ]:
100
+ factors = list(remaining_factors)
101
+ for m in factors:
102
+ if verbose > 1: print("Factoring: ",m, "method", methods[method])
103
+ if method == factor_pm1:
104
+ tmp = factor_pm1(m, pm1_parameters = pm1_parameters)
105
+ elif method == factor_ecm:
106
+ tmp = factor_ecm(m, ecm_parameters = ecm_parameters)
107
+ else:
108
+ tmp = method(m)
109
+ if tmp:
110
+ tmp2 = m // tmp
111
+ g = gcd(tmp, tmp2)
112
+ if g > 1:
113
+ tmp3 = [ g, g ]
114
+ for x in (tmp, tmp2):
115
+ x //= g
116
+ while x % g == 0:
117
+ tmp3.append(g)
118
+ x //= g
119
+ if x > 1:
120
+ tmp3.append(x)
121
+ else:
122
+ tmp3 = [tmp, tmp2]
123
+ if verbose > 0: print("Factors found (", methods[method] ,"): ", tmp3)
124
+ add_factors(m, tmp3)
125
+ if not new_factors:
126
+ break
127
+ for m in new_factors:
128
+ if m in remaining_factors:
129
+ remaining_factors[m] += new_factors[m]
130
+ else:
131
+ remaining_factors[m] = new_factors[m]
132
+ if verbose > 1: print("Remaining: ", remaining_factors)
133
+
134
+ if len(remaining_factors) == 0:
135
+ return prime_factors
136
+
137
+ print("Incomplete factorization!")
138
+ return prime_factors, remaining_factors, new_factors