kryptools 1.1__tar.gz → 1.2__tar.gz

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.
Files changed (56) hide show
  1. {kryptools-1.1 → kryptools-1.2}/PKG-INFO +3 -5
  2. {kryptools-1.1 → kryptools-1.2}/README.md +2 -4
  3. kryptools-1.2/kryptools/GF2.py +351 -0
  4. {kryptools-1.1 → kryptools-1.2}/kryptools/Zmod.py +16 -10
  5. {kryptools-1.1 → kryptools-1.2}/kryptools/__init__.py +1 -1
  6. {kryptools-1.1 → kryptools-1.2}/kryptools/aes.py +8 -4
  7. {kryptools-1.1 → kryptools-1.2}/kryptools/des.py +2 -2
  8. {kryptools-1.1 → kryptools-1.2}/kryptools/dlp.py +3 -3
  9. {kryptools-1.1 → kryptools-1.2}/kryptools/dlp_bsgs.py +1 -1
  10. {kryptools-1.1 → kryptools-1.2}/kryptools/dlp_ic.py +1 -1
  11. {kryptools-1.1 → kryptools-1.2}/kryptools/dlp_qs.py +3 -3
  12. {kryptools-1.1 → kryptools-1.2}/kryptools/dlp_rho.py +1 -1
  13. {kryptools-1.1 → kryptools-1.2}/kryptools/factor_dix.py +3 -2
  14. {kryptools-1.1 → kryptools-1.2}/kryptools/factor_ecm.py +1 -1
  15. {kryptools-1.1 → kryptools-1.2}/kryptools/factor_fmt.py +1 -1
  16. {kryptools-1.1 → kryptools-1.2}/kryptools/factor_pm1.py +1 -1
  17. {kryptools-1.1 → kryptools-1.2}/kryptools/factor_qs.py +5 -4
  18. {kryptools-1.1 → kryptools-1.2}/kryptools/factor_rho.py +1 -1
  19. {kryptools-1.1 → kryptools-1.2}/kryptools/keccak.py +9 -5
  20. {kryptools-1.1 → kryptools-1.2}/kryptools/la.py +107 -2
  21. {kryptools-1.1 → kryptools-1.2}/kryptools/poly.py +68 -8
  22. {kryptools-1.1 → kryptools-1.2}/kryptools/sha1.py +16 -12
  23. {kryptools-1.1 → kryptools-1.2}/kryptools.egg-info/PKG-INFO +3 -5
  24. {kryptools-1.1 → kryptools-1.2}/pyproject.toml +1 -1
  25. {kryptools-1.1 → kryptools-1.2}/tests/test_GF2.py +5 -1
  26. {kryptools-1.1 → kryptools-1.2}/tests/test_Zmod.py +2 -0
  27. {kryptools-1.1 → kryptools-1.2}/tests/test_sha1.py +1 -1
  28. kryptools-1.1/kryptools/GF2.py +0 -281
  29. {kryptools-1.1 → kryptools-1.2}/LICENSE +0 -0
  30. {kryptools-1.1 → kryptools-1.2}/kryptools/blockcipher.py +0 -0
  31. {kryptools-1.1 → kryptools-1.2}/kryptools/conway_polynomials.py +0 -0
  32. {kryptools-1.1 → kryptools-1.2}/kryptools/ec.py +0 -0
  33. {kryptools-1.1 → kryptools-1.2}/kryptools/factor.py +0 -0
  34. {kryptools-1.1 → kryptools-1.2}/kryptools/intfuncs.py +0 -0
  35. {kryptools-1.1 → kryptools-1.2}/kryptools/lat.py +0 -0
  36. {kryptools-1.1 → kryptools-1.2}/kryptools/nt.py +0 -0
  37. {kryptools-1.1 → kryptools-1.2}/kryptools/primes.py +0 -0
  38. {kryptools-1.1 → kryptools-1.2}/kryptools.egg-info/SOURCES.txt +0 -0
  39. {kryptools-1.1 → kryptools-1.2}/kryptools.egg-info/dependency_links.txt +0 -0
  40. {kryptools-1.1 → kryptools-1.2}/kryptools.egg-info/top_level.txt +0 -0
  41. {kryptools-1.1 → kryptools-1.2}/setup.cfg +0 -0
  42. {kryptools-1.1 → kryptools-1.2}/tests/test_aes.py +0 -0
  43. {kryptools-1.1 → kryptools-1.2}/tests/test_des.py +0 -0
  44. {kryptools-1.1 → kryptools-1.2}/tests/test_dlog.py +0 -0
  45. {kryptools-1.1 → kryptools-1.2}/tests/test_ec.py +0 -0
  46. {kryptools-1.1 → kryptools-1.2}/tests/test_factor.py +0 -0
  47. {kryptools-1.1 → kryptools-1.2}/tests/test_factor_fmt.py +0 -0
  48. {kryptools-1.1 → kryptools-1.2}/tests/test_factor_pm1.py +0 -0
  49. {kryptools-1.1 → kryptools-1.2}/tests/test_factor_qs.py +0 -0
  50. {kryptools-1.1 → kryptools-1.2}/tests/test_intfuncs.py +0 -0
  51. {kryptools-1.1 → kryptools-1.2}/tests/test_keccak.py +0 -0
  52. {kryptools-1.1 → kryptools-1.2}/tests/test_la.py +0 -0
  53. {kryptools-1.1 → kryptools-1.2}/tests/test_lat.py +0 -0
  54. {kryptools-1.1 → kryptools-1.2}/tests/test_nt.py +0 -0
  55. {kryptools-1.1 → kryptools-1.2}/tests/test_poly.py +0 -0
  56. {kryptools-1.1 → kryptools-1.2}/tests/test_primes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.1
3
+ Version: 1.2
4
4
  Summary: Implemenation of same basic algorithms used in cryptography.
5
5
  Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
6
6
  License: MIT
@@ -49,10 +49,8 @@ The tools contained are:
49
49
  * AESCipher: individual AES operations
50
50
  * DESCipher: individual DES operations
51
51
 
52
- * SHA1
53
- * Keccak individual sponge operations
54
- * SHA3
55
- * SHAKE
52
+ * SHA1: custom initial state, padding function
53
+ * Keccak: individual sponge operations, SHA3, SHAKE
56
54
 
57
55
  Documentation can be found in the jupyter notebook
58
56
  (mostly done, but might not contain everything: use the force, read the source).
@@ -31,10 +31,8 @@ The tools contained are:
31
31
  * AESCipher: individual AES operations
32
32
  * DESCipher: individual DES operations
33
33
 
34
- * SHA1
35
- * Keccak individual sponge operations
36
- * SHA3
37
- * SHAKE
34
+ * SHA1: custom initial state, padding function
35
+ * Keccak: individual sponge operations, SHA3, SHAKE
38
36
 
39
37
  Documentation can be found in the jupyter notebook
40
38
  (mostly done, but might not contain everything: use the force, read the source).
@@ -0,0 +1,351 @@
1
+ """
2
+ Galois field GF(2^n).
3
+ """
4
+
5
+ from math import gcd
6
+ from .poly import Poly
7
+ from .Zmod import Zmod
8
+ from .factor import factorint
9
+ from .conway_polynomials import conway_polynomials2
10
+
11
+
12
+ class GF2:
13
+ """
14
+ Create a Galois field GF(2^n).
15
+
16
+ Elements of a Galois filed are specified by integers from 0 to 2**n-1, which are
17
+ identified with polynomials whose coefficients are the binary digits of the integer.
18
+ Multiplication is polynomial multiplication modulo a given irreducible polynomial.
19
+ If the polynomial has the n'th bit set, the lowest bit corresponds to the constant coefficient
20
+ (like in AES). Otherwise, the bit order is reversed (like for Ghash). The default modulus is
21
+ choosen from a list of Conway polynomials.
22
+
23
+ Example:
24
+
25
+ To define the Galois field GF(2^8) use
26
+ >>> gf=GF2(8)
27
+
28
+ To declare 3 as an element of our Galois field use (elements are displayed as hex numbers)
29
+ >>> gf(3)
30
+ 03
31
+
32
+ The usual arithmetic operations are supported.
33
+ >>> gf(2) * gf(123)
34
+ f6
35
+
36
+ """
37
+ def __init__(self, n: int, modulus: int | None = None):
38
+ if not isinstance(n, int) or n < 1:
39
+ raise ValueError(f"{n} is not a positive integer.")
40
+ if not modulus:
41
+ if n in conway_polynomials2:
42
+ modulus = conway_polynomials2[n]
43
+ else:
44
+ raise ValueError("Unknown power. Please specify a modulus.")
45
+ self.modulus = modulus # integer whose biniary digits are the coefficients of the irreducible modulus polynomial
46
+ if modulus & 2**n == 2**n:
47
+ self.bitreversed = False
48
+ else:
49
+ self.bitreversed = True
50
+ self.power = n # n
51
+ self.order = 2**n # 2**n
52
+ self.print_hex: bool = True
53
+ self.byteorder: str = "big" # big|little
54
+ self.mult_order = self.order -1
55
+ self.factors = {} # factoring of the group order
56
+
57
+ def __repr__(self):
58
+ return f"GF(2^{self.power})"
59
+
60
+ def __call__(self, x: int | list | tuple):
61
+ if isinstance(x, list|tuple):
62
+ return [GF2nPoint(xx, self) for xx in x]
63
+ return GF2nPoint(x, self)
64
+
65
+ def __iter__(self):
66
+ for x in range(self.order):
67
+ yield GF2nPoint(x, self)
68
+
69
+ def __eq__(self, other):
70
+ if isinstance(other, self.__class__):
71
+ return self.order == other.order and self.modulus == other.modulus
72
+ return False
73
+
74
+ def __contains__(self, other: "ZmodPoint") -> bool:
75
+ return isinstance(other, GF2nPoint) and self == other.field
76
+
77
+ def _factors(self) -> dict:
78
+ "Return the factroization order of the group order of GF(2^n)^*."
79
+ if not self.factors:
80
+ self.factors = factorint(self.mult_order)
81
+ return self.factors
82
+
83
+ def one(self) -> bool:
84
+ "Return one."
85
+ if self.bitreversed:
86
+ return self(self.order - 1)
87
+ return self(1)
88
+
89
+ def is_cyclic(self) -> bool:
90
+ "GF(2^n)^* is cyclic."
91
+ return True
92
+
93
+ def is_field(self) -> bool:
94
+ "GF(2^n) is a field."
95
+ return True
96
+
97
+ def generator(self) -> int | None:
98
+ "Return a generator of the group GF(2^n)^*."
99
+ for a in range(2, self.order):
100
+ a = self(a)
101
+ if a.is_generator():
102
+ return a
103
+
104
+ def generators(self) -> list | None:
105
+ "Return a generator for all generators of the group GF(2^n)^*."
106
+ a = self.generator()
107
+ if a is not None:
108
+ for j in range(1, self.mult_order):
109
+ if gcd(j, self.mult_order) == 1:
110
+ yield a**j
111
+
112
+ def star(self) -> list:
113
+ "Return a generator for all elements of the group GF(2^n)n^*."
114
+ for x in range(1, self.order):
115
+ yield self(x)
116
+
117
+
118
+ class GF2nPoint:
119
+ "Represents a point in the Galois field GF(2^n)."
120
+
121
+ def __init__(self, x: int, field: "GF2n"):
122
+ if isinstance(x, bytes | bytearray):
123
+ x = int.from_bytes(x, byteorder=field.byteorder)
124
+ self.x = int(x) % field.order
125
+ self.field = field
126
+
127
+ def __repr__(self):
128
+ if self.field.print_hex:
129
+ return format(self.x, "0"+str(self.field.power // 4)+"x")
130
+ return self.x
131
+
132
+ def __eq__(self, other):
133
+ if not isinstance(other, self.__class__) or self.field != other.field:
134
+ return False
135
+ return self.x == other.x
136
+
137
+ def __bool__(self):
138
+ return bool(self.x)
139
+
140
+ def __int__(self):
141
+ return self.x
142
+
143
+ def __bytes__(self):
144
+ return self.x.to_bytes(self.field.power // 8, byteorder=self.field.byteorder)
145
+
146
+ def bits(self) -> list:
147
+ "Convert to a list of bits."
148
+ if self.field.bitreversed:
149
+ coeff = [int(d) for d in str(
150
+ format(self.x, "0" + str(self.field.power) + "b"))]
151
+ else:
152
+ coeff = reversed([int(d) for d in str(
153
+ format(self.x, "0" + str(self.field.power) + "b"))])
154
+ return list(coeff)
155
+
156
+ def poly(self) -> "Poly":
157
+ "Convert to a polynomial."
158
+ Z_2 = Zmod(2)
159
+ if self.field.bitreversed:
160
+ coeff = [Z_2(int(d)) for d in str(
161
+ format(self.x, "0" + str(self.field.power) + "b"))]
162
+ coeff_modulus = [Z_2(int(d)) for d in str(
163
+ format((self.field.modulus << 1) + 1, "0" + str(self.field.power) + "b"))]
164
+ else:
165
+ coeff = reversed([Z_2(int(d)) for d in str(
166
+ format(self.x, "0" + str(self.field.power) + "b"))])
167
+ coeff_modulus = reversed([Z_2(int(d)) for d in str(
168
+ format(self.field.modulus, "0" + str(self.field.power) + "b"))])
169
+ return Poly(list(coeff), modulus=list(coeff_modulus))
170
+
171
+ def __hash__(self):
172
+ return hash(self.x)
173
+
174
+ def __add__(self, other: "GF2nPoint") -> "GF2nPoint":
175
+ if not isinstance(other, self.__class__):
176
+ return NotImplemented
177
+ if self.field != other.field:
178
+ raise NotImplementedError("Cannot add elements from different fields.")
179
+ return self.field(self.x ^ other.x)
180
+
181
+ def __neg__(self) -> "GF2nPoint":
182
+ return self
183
+
184
+ def __pos__(self) -> "GF2nPoint":
185
+ return self
186
+
187
+ def __sub__(self, other: "GF2nPoint") -> "GF2nPoint":
188
+ if not isinstance(other, self.__class__):
189
+ return NotImplemented
190
+ if self.field != other.field:
191
+ raise NotImplementedError("Cannot add elements from different fields.")
192
+ return self.field(self.x ^ other.x)
193
+
194
+ def __mul__(self, other: "GF2nPoint") -> "GF2nPoint":
195
+ if isinstance(other, int):
196
+ if other % 2:
197
+ return self
198
+ return self.field(0)
199
+ if not isinstance(other, self.__class__):
200
+ return NotImplemented
201
+ if self.field != other.field:
202
+ raise NotImplementedError("Cannot add elements from different fields.")
203
+ z = 0
204
+ y = other.x
205
+ if self.field.bitreversed:
206
+ for d in bin(self.x)[2:].zfill(self.field.power):
207
+ if int(d):
208
+ z ^= y
209
+ if y % 2:
210
+ y >>= 1
211
+ y ^= self.field.modulus
212
+ else:
213
+ y >>= 1
214
+ else:
215
+ bitmap1 = self.field.order
216
+ bitmap2 = 1
217
+ for _ in range(self.field.power):
218
+ if self.x & bitmap2:
219
+ z ^= y
220
+ bitmap2 <<= 1
221
+ y <<= 1
222
+ if y & bitmap1:
223
+ y ^= self.field.modulus
224
+ return self.field(z)
225
+
226
+ def __rmul__(self, scalar: int) -> "GF2nPoint":
227
+ if isinstance(scalar, int):
228
+ if scalar % 2:
229
+ return self
230
+ return self.field(0)
231
+ return NotImplemented
232
+
233
+ def __truediv__(self, other: "GF2nPoint") -> "GF2nPoint":
234
+ if not isinstance(other, self.__class__):
235
+ return NotImplemented
236
+ if self.field != other.field:
237
+ raise NotImplementedError("Cannot add elements from different fields.")
238
+ if not other.x:
239
+ raise ValueError("Division by zero.")
240
+ return self * other**(self.field.order - 2)
241
+
242
+ def __pow__(self, j: int) -> "GF2nPoint":
243
+ if not isinstance(j, int):
244
+ return NotImplemented
245
+ if not (self.x) and j < 0:
246
+ raise ValueError("Division by zero.")
247
+ if self.field.bitreversed:
248
+ res = self.field(self.field.order >> 1)
249
+ else:
250
+ res = self.field(1)
251
+ x = self.field(self.x)
252
+ j %= self.field.order - 1
253
+ while j > 0:
254
+ # If j is odd, multiply with x
255
+ if j & 1:
256
+ res *= x
257
+ # Now square
258
+ j = j >> 1 # j= j/2
259
+ x *= x
260
+ return res
261
+
262
+ def __lshift__(self, i: int) -> "GF2nPoint":
263
+ "Cyclic rotation to the left."
264
+ x = self.x << i
265
+ x = (x % self.field.order) + (x // self.field.order)
266
+ return self.field(x)
267
+
268
+ def __rshift__(self, i: int) -> "GF2nPoint":
269
+ "Cyclic rotation to the right."
270
+ x = self.x >> i
271
+ x += (self.field.order >> i) * (self.x % 2**i)
272
+ return self.field(x)
273
+
274
+ def sqrt(self) -> "GF2nPoint":
275
+ "Compute the square root."
276
+ return self**(self.field.order//2)
277
+
278
+ def order(self) -> int:
279
+ "Compute the order of the point in the group GF(2^n)^*."
280
+ self.field._factors()
281
+ order = self.field.mult_order # our current guess
282
+ one = self.field.one()
283
+ for p, k in self.field.factors.items():
284
+ for _ in range(k):
285
+ order_try = order // p
286
+ if self ** order_try == one:
287
+ order = order_try
288
+ else:
289
+ break
290
+ return order
291
+
292
+ def is_generator(self):
293
+ "Test if the point is a generator of the group GF(2^n)^*."
294
+ return self.field.mult_order == self.order()
295
+
296
+ def sbox(self, inv: bool = False) -> "GF2nPoint":
297
+ "Apply the AES sbox."
298
+ if self.field.power == 8 and self.field.modulus == 0b100011011: # AES
299
+ if inv:
300
+ return aes_sbox_inv[self.x]
301
+ return aes_sbox[self.x]
302
+ elif self.field.power == 4 and self.field.modulus == 0b10011: # MiniAES
303
+ if inv:
304
+ return miniaes_sbox_inv[self.x]
305
+ return miniaes_sbox[self.x]
306
+ raise ValueError("sbox is only availaible for AES/MiniAES field.")
307
+
308
+ # sbox for AES
309
+ GF2_aes = GF2(8, 0b100011011) # x^8 + x^4 + x^3 + x + 1 = AES
310
+
311
+ aes_sbox = [99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118,
312
+ 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192,
313
+ 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21,
314
+ 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117, 9,
315
+ 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132, 83,
316
+ 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, 208,
317
+ 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81,
318
+ 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205,
319
+ 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96,
320
+ 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224,
321
+ 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231,
322
+ 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186,
323
+ 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112,
324
+ 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225,
325
+ 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140,
326
+ 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22]
327
+
328
+ # inverse sbox for AES
329
+ aes_sbox_inv = [ 0 for i in range(256) ]
330
+ for i in range(256):
331
+ aes_sbox_inv[aes_sbox[i]] = i
332
+ aes_sbox = list(map(GF2_aes, aes_sbox))
333
+ aes_sbox_inv = list(map(GF2_aes, aes_sbox_inv))
334
+
335
+
336
+ # sbox for Mini-AES
337
+ GF2_miniaes = GF2(4, 0b10011) # x^4 + x + 1 = Mini-AES
338
+
339
+ miniaes_sbox = [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7]
340
+
341
+ # inverse sbox for MiniAES
342
+ miniaes_sbox_inv = [ 0 for i in range(16) ]
343
+ for i in range(16):
344
+ miniaes_sbox_inv[miniaes_sbox[i]] = i
345
+
346
+ miniaes_sbox = list(map(GF2_miniaes, miniaes_sbox))
347
+ miniaes_sbox_inv = list(map(GF2_miniaes, miniaes_sbox_inv))
348
+
349
+ #Ghash
350
+
351
+ GF2_ghash = GF2(128, 0b11100001 << 120) # 1 + x + x^2 + x^7 (+ x^128) = Ghash
@@ -4,6 +4,7 @@ Ring of intergers modulo `n`.
4
4
 
5
5
  from math import gcd
6
6
  from .factor import factorint
7
+ from .primes import is_prime
7
8
  from .intfuncs import prime_power
8
9
 
9
10
 
@@ -33,11 +34,12 @@ class Zmod:
33
34
  self.group_order = 0
34
35
  self.factors = {} # factoring of the group order
35
36
 
37
+ def __repr__(self):
38
+ return f"Z_{self.n})"
39
+
36
40
  def __call__(self, x: int | list | tuple):
37
- if isinstance(x, list):
41
+ if isinstance(x, list|tuple):
38
42
  return [ZmodPoint(xx, self) for xx in x]
39
- if isinstance(x, tuple):
40
- return (ZmodPoint(xx, self) for xx in x)
41
43
  return ZmodPoint(x, self)
42
44
 
43
45
  def __iter__(self):
@@ -53,7 +55,7 @@ class Zmod:
53
55
  return isinstance(other, ZmodPoint) and self.n == other.ring.n
54
56
 
55
57
  def order(self) -> int:
56
- """Compute the order of the group Z_n^*."""
58
+ "Compute the order of the group Z_n^*."
57
59
  if self.group_order:
58
60
  return self.group_order
59
61
  # We compute euler_phi(n) and its factorization in one pass
@@ -76,7 +78,7 @@ class Zmod:
76
78
  return self.group_order
77
79
 
78
80
  def is_cyclic(self) -> bool:
79
- """Test if the group Z_n^* is cyclic."""
81
+ "Test if the group Z_n^* is cyclic."
80
82
  n = self.n
81
83
  if n < 8:
82
84
  return True
@@ -88,8 +90,12 @@ class Zmod:
88
90
  return True
89
91
  return False
90
92
 
93
+ def is_field(self) -> bool:
94
+ "Test if Z_n is a field."
95
+ return is_prime(self.n)
96
+
91
97
  def generator(self) -> int | None:
92
- """Return a generator of the group Z_n^*."""
98
+ "Return a generator of the group Z_n^*."
93
99
  if not self.is_cyclic():
94
100
  return None
95
101
  for a in range(2, self.n):
@@ -100,7 +106,7 @@ class Zmod:
100
106
  return a
101
107
 
102
108
  def generators(self) -> list | None:
103
- """Return a generator for all generators of the group Z_n^*."""
109
+ "Return a generator for all generators of the group Z_n^*."
104
110
  a = self.generator()
105
111
  if a is not None:
106
112
  self.order()
@@ -109,7 +115,7 @@ class Zmod:
109
115
  yield a**j
110
116
 
111
117
  def star(self) -> list:
112
- """Return a generator for all elements of the group Z_n^*."""
118
+ "Return a generator for all elements of the group Z_n^*."
113
119
  yield self(1)
114
120
  for a in range(2, self.n):
115
121
  if gcd(a, self.n) == 1:
@@ -210,7 +216,7 @@ class ZmodPoint:
210
216
  return (self.x + tmp) % self.ring.n - tmp
211
217
 
212
218
  def order(self) -> int:
213
- """Compute the order of the point in the group Z_n^*."""
219
+ "Compute the order of the point in the group Z_n^*."
214
220
  if self.x == 0 or gcd(self.x, self.ring.n) != 1:
215
221
  raise ValueError(f"{self.x} and {self.ring.n} are not coprime!")
216
222
  order = self.ring.order() # use euler_phi(n) as our current guess
@@ -224,5 +230,5 @@ class ZmodPoint:
224
230
  return order
225
231
 
226
232
  def is_generator(self):
227
- """Test if the point is a generator of the group Z_n^*."""
233
+ "Test if the point is a generator of the group Z_n^*."
228
234
  return self.ring.order() == self.order()
@@ -5,7 +5,7 @@ Implemenation of same basic algorithms used in cryptography.
5
5
  __author__ = "Gerald Teschl"
6
6
  __copyright__ = "Copyright 2024-2025, Gerald Teschl"
7
7
  __license__ = "MIT License"
8
- __version__ = "1.1"
8
+ __version__ = "1.2"
9
9
  __email__ = "Gerald.Teschl@univie.ac.at"
10
10
 
11
11
  from .nt import egcd, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, carmichael_lambda, moebius_mu, is_carmichael_number, order, crt
@@ -143,15 +143,19 @@ class AESKeySchedule:
143
143
  self.round += 1
144
144
  #RotWord
145
145
  tmp = list(self.key[:,3])
146
- if self.showtmp: print("Start :" + str(tmp))
146
+ if self.showtmp:
147
+ print("Start :" + str(tmp))
147
148
  tmp = rotate_left(tmp, 1)
148
- if self.showtmp: print("RotWord :" + str(tmp))
149
+ if self.showtmp:
150
+ print("RotWord :" + str(tmp))
149
151
  #SubWord
150
152
  tmp = list(map(lambda x: x.sbox(), tmp))
151
- if self.showtmp: print("SubWord :" + str(tmp))
153
+ if self.showtmp:
154
+ print("SubWord :" + str(tmp))
152
155
  #AddRound constant
153
156
  tmp[0] += GF2_aes(2)**(self.round - 1)
154
- if self.showtmp: print("AddConst :" + str(tmp))
157
+ if self.showtmp:
158
+ print("AddConst :" + str(tmp))
155
159
  tmp = Matrix(tmp)
156
160
  self.key[:,0] += tmp
157
161
  self.key[:,1] += self.key[:,0]
@@ -168,10 +168,10 @@ class FeistelCipher:
168
168
  def f_box(self, a: list[int], key: list[int], showtmp:bool = False) -> list[int]:
169
169
  "Nonlinear f-box"
170
170
  if showtmp:
171
- def printtmp(title:str, a: list) -> None:
171
+ def printtmp(title: str, a: list) -> None:
172
172
  print(f'{title}: {"".join(map(str,a))}')
173
173
  else:
174
- def printtmp(title:str, a: list) -> None:
174
+ def printtmp(title: str, a: list) -> None:
175
175
  pass
176
176
  a = permute(a, self.__class__.E_box) # expansion box
177
177
  printtmp("EBox", a)
@@ -12,7 +12,7 @@ from .factor import factorint
12
12
 
13
13
 
14
14
  def dlog_naive(a: int, b: int, n: int, m: int = None, verbose: int = 0) -> int | None:
15
- """Compute the discrete log_a(b) in Z_n of an element a of order m by exhaustive search."""
15
+ """Compute the discrete log_a(b) in Z_n of an element `a` of order `m` by exhaustive search."""
16
16
  a %= n
17
17
  b %= n
18
18
  if not m:
@@ -35,7 +35,7 @@ def dlog_naive(a: int, b: int, n: int, m: int = None, verbose: int = 0) -> int |
35
35
 
36
36
 
37
37
  def _dlog_switch(a: int, b: int, n: int, m: int, verbose: int = 0) -> int:
38
- """Compute the discrete log_a(b) in Z_n of an element a of order m choosing an appropriate method."""
38
+ """Compute the discrete log_a(b) in Z_n of an element `a` of order `m` choosing an appropriate method."""
39
39
  if m < 1000:
40
40
  if verbose > 1:
41
41
  print(f"Naive search: {a}^x = {b} (order={m})")
@@ -51,7 +51,7 @@ def _dlog_switch(a: int, b: int, n: int, m: int, verbose: int = 0) -> int:
51
51
 
52
52
 
53
53
  def _dlog_ph(a: int, b: int, n: int, q: int, k: int, verbose: int = 0) -> int:
54
- """Compute the discrete log_a(b) in Z_n of an element a of order q^k using Pohlig-Hellman reduction."""
54
+ """Compute the discrete log_a(b) in Z_n of an element `a` of order `q^k` using Pohlig-Hellman reduction."""
55
55
  if verbose > 1:
56
56
  print(f"PH reduction: {a}^x = {b} (order={q}^{k})")
57
57
  if k == 1 or q**k < 10000:
@@ -6,7 +6,7 @@ from math import isqrt
6
6
 
7
7
 
8
8
  def dlog_bsgs(a: int, b: int, n: int, m: int = None, verbose: int = 0) -> int:
9
- """Compute the discrete log_a(b) in Z_n of an element a of order m using Shanks' baby-step-giant-step algorithm."""
9
+ """Compute the discrete log_a(b) in Z_n of an element `a` of order `m` using Shanks' baby-step-giant-step algorithm."""
10
10
  a %= n
11
11
  b %= n
12
12
  if not m:
@@ -10,7 +10,7 @@ seed(0)
10
10
 
11
11
 
12
12
  def dlog_ic(a: int, b: int, n: int, m: int, pollard: bool = True, verbose: int = 0) -> int:
13
- """Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus."""
13
+ """Compute the discrete log_a(b) in Z_p of an element `a` of prime order `m` using Index Calculus."""
14
14
 
15
15
  # assert is_prime(m), "The order of a must be prime."
16
16
  # assert order(a, n) == m, "The order of a is incorrect."
@@ -10,7 +10,7 @@ seed(0)
10
10
 
11
11
 
12
12
  def determine_factorbound(n: int) -> (int, int):
13
- """Determines the optimal factor bound and the expected number of trys until a for a given n."""
13
+ """Determines the optimal factor bound and the expected number of trys until a relation is found for a given `n`."""
14
14
  # Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us the expected running time |factorbase|^2 u^u = u^(u+2) n^(2/u)/log(n).
15
15
  # There is no explicit expression for the optimum, hence we use Newton
16
16
  u = 2 * sqrt(log(n) / log(log(n))) # asymptotic value
@@ -47,7 +47,7 @@ def determine_trialdivison_bounds(B: int, factorbase: list) -> (int, int):
47
47
 
48
48
 
49
49
  def is_smooth(n: int, factorbase: list, factorbase_len: int, smallprimes_len: int, pollard_k: int = None) -> list or None:
50
- """Try to factor n with respect to a a given factorbase. Upon success a list of exponents with repect to the factorbase is returned. Otherwise None."""
50
+ """Try to factor `n` with respect to a given factorbase. Upon success a list of exponents with repect to the factorbase is returned. Otherwise None."""
51
51
  # factorbase_len = len(factorbase)
52
52
  factors = [0] * factorbase_len
53
53
  for i in range(smallprimes_len):
@@ -70,7 +70,7 @@ def is_smooth(n: int, factorbase: list, factorbase_len: int, smallprimes_len: in
70
70
 
71
71
  def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor: float = None, verbose: int = 0) -> int:
72
72
  """
73
- Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus with a quadratic sieve.
73
+ Compute the discrete log_a(b) in Z_p of an element `a` of prime order `m` using Index Calculus with a quadratic sieve.
74
74
  The problem is assumed solvable.
75
75
  """
76
76
  # assert is_prime(m), "The order of a must be prime."
@@ -7,7 +7,7 @@ from random import randint
7
7
 
8
8
 
9
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."""
10
+ """Compute the discrete log_a(b) in Z_p of an element `a` of order `m` using Pollard's rho algorithm."""
11
11
  a %= n
12
12
  b %= n
13
13
  if not m:
@@ -9,7 +9,7 @@ from .factor_qs import bytexor, byteset, bytetest
9
9
 
10
10
 
11
11
  def is_smooth(n: int, factorbase: list, lfb: int) -> bytes | None:
12
- """Try to factor n with respect to a given factorbase.
12
+ """Try to factor `n` with respect to a given factorbase.
13
13
  Upon success a bytestring, whose bits are the exponents with repect to the factorbase mod 2, is returned.
14
14
  Otherwise None."""
15
15
  factors = bytearray(b"\x00") * lfb # we store the exponents mod 2 as bits
@@ -29,7 +29,7 @@ def is_smooth(n: int, factorbase: list, lfb: int) -> bytes | None:
29
29
 
30
30
 
31
31
  def factor_dixon(n: int) -> int | None:
32
- """Find factors of n using the method of Dixon."""
32
+ """Find factors of `n` using the method of Dixon."""
33
33
  # first determine the bound B for the factorbase: Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us
34
34
  # the expected running time |B|^2 u^u = u^(u+2) p^(2/u)/log(n). There is no explicit expression for the optimum, hence
35
35
  # we use Newton
@@ -55,6 +55,7 @@ def factor_dixon(n: int) -> int | None:
55
55
  m = isqrt(n - 1) + 1
56
56
 
57
57
  def process_relation(j: int, relation: bytes):
58
+ "Add a relation to the linear system and reduce the new system."
58
59
  nonlocal relation_no, values, relations
59
60
  relation_no += 1
60
61
  # construct the k'th row of the identity matrix
@@ -89,7 +89,7 @@ def _ecm_parameters(B1: int, B2: int | None = None, D: int | None = None, primes
89
89
 
90
90
 
91
91
  def factor_ecm(n: int, B1: int | None = None, B2: int | None = None, curves: int = 150, ecm_parameters: tuple | None = None, verbose: int = 0) -> int | None:
92
- "Factors a number n using Lentsta's ECM method."
92
+ "Factors a number `n` using Lentsta's ECM method."
93
93
 
94
94
  if ecm_parameters:
95
95
  B1, B2, curves, D, stage_one, stage_two_deltas = ecm_parameters
@@ -6,7 +6,7 @@ from math import isqrt, floor, log10
6
6
 
7
7
 
8
8
  def factor_fermat(n: int, maxsteps: int | None = None, verbose: int = 0) -> list:
9
- """Find factors of n using the method of Fermat."""
9
+ """Find factors of `n` using the method of Fermat."""
10
10
  if not isinstance(n, int) or n < 1:
11
11
  raise ValueError("Number to be factored must be a positive integer!")
12
12
  if verbose:
@@ -27,7 +27,7 @@ def _pm1_parameters(B1: int, B2: int, primes: tuple = None) -> tuple:
27
27
 
28
28
 
29
29
  def factor_pm1(n: int, B1: int | None = None, B2: int | None = None, x: int = 2, pm1_parameters: tuple = None, verbose: int = 0) -> int | None:
30
- "Factors a number n using Pollard's p-1 method."
30
+ "Factors a number `n` using Pollard's p-1 method."
31
31
  if pm1_parameters:
32
32
  B1, B2, stage_one, stage_two_deltas = pm1_parameters
33
33
  else: