kryptools 1.1__tar.gz → 1.3__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 (58) hide show
  1. {kryptools-1.1 → kryptools-1.3}/PKG-INFO +5 -5
  2. {kryptools-1.1 → kryptools-1.3}/README.md +4 -4
  3. kryptools-1.3/kryptools/GF2.py +351 -0
  4. {kryptools-1.1 → kryptools-1.3}/kryptools/Zmod.py +21 -11
  5. {kryptools-1.1 → kryptools-1.3}/kryptools/__init__.py +2 -1
  6. {kryptools-1.1 → kryptools-1.3}/kryptools/aes.py +8 -4
  7. kryptools-1.3/kryptools/code.py +124 -0
  8. {kryptools-1.1 → kryptools-1.3}/kryptools/des.py +2 -2
  9. {kryptools-1.1 → kryptools-1.3}/kryptools/dlp.py +3 -3
  10. {kryptools-1.1 → kryptools-1.3}/kryptools/dlp_bsgs.py +1 -1
  11. {kryptools-1.1 → kryptools-1.3}/kryptools/dlp_ic.py +1 -1
  12. {kryptools-1.1 → kryptools-1.3}/kryptools/dlp_qs.py +3 -3
  13. {kryptools-1.1 → kryptools-1.3}/kryptools/dlp_rho.py +1 -1
  14. {kryptools-1.1 → kryptools-1.3}/kryptools/factor_dix.py +3 -2
  15. {kryptools-1.1 → kryptools-1.3}/kryptools/factor_ecm.py +1 -1
  16. {kryptools-1.1 → kryptools-1.3}/kryptools/factor_fmt.py +1 -1
  17. {kryptools-1.1 → kryptools-1.3}/kryptools/factor_pm1.py +1 -1
  18. {kryptools-1.1 → kryptools-1.3}/kryptools/factor_qs.py +5 -4
  19. {kryptools-1.1 → kryptools-1.3}/kryptools/factor_rho.py +1 -1
  20. {kryptools-1.1 → kryptools-1.3}/kryptools/keccak.py +9 -5
  21. {kryptools-1.1 → kryptools-1.3}/kryptools/la.py +107 -2
  22. {kryptools-1.1 → kryptools-1.3}/kryptools/lat.py +2 -1
  23. {kryptools-1.1 → kryptools-1.3}/kryptools/poly.py +78 -8
  24. {kryptools-1.1 → kryptools-1.3}/kryptools/sha1.py +16 -12
  25. {kryptools-1.1 → kryptools-1.3}/kryptools.egg-info/PKG-INFO +5 -5
  26. {kryptools-1.1 → kryptools-1.3}/kryptools.egg-info/SOURCES.txt +2 -0
  27. {kryptools-1.1 → kryptools-1.3}/pyproject.toml +1 -1
  28. {kryptools-1.1 → kryptools-1.3}/tests/test_GF2.py +5 -1
  29. {kryptools-1.1 → kryptools-1.3}/tests/test_Zmod.py +2 -0
  30. kryptools-1.3/tests/test_code.py +32 -0
  31. {kryptools-1.1 → kryptools-1.3}/tests/test_sha1.py +1 -1
  32. kryptools-1.1/kryptools/GF2.py +0 -281
  33. {kryptools-1.1 → kryptools-1.3}/LICENSE +0 -0
  34. {kryptools-1.1 → kryptools-1.3}/kryptools/blockcipher.py +0 -0
  35. {kryptools-1.1 → kryptools-1.3}/kryptools/conway_polynomials.py +0 -0
  36. {kryptools-1.1 → kryptools-1.3}/kryptools/ec.py +0 -0
  37. {kryptools-1.1 → kryptools-1.3}/kryptools/factor.py +0 -0
  38. {kryptools-1.1 → kryptools-1.3}/kryptools/intfuncs.py +0 -0
  39. {kryptools-1.1 → kryptools-1.3}/kryptools/nt.py +0 -0
  40. {kryptools-1.1 → kryptools-1.3}/kryptools/primes.py +0 -0
  41. {kryptools-1.1 → kryptools-1.3}/kryptools.egg-info/dependency_links.txt +0 -0
  42. {kryptools-1.1 → kryptools-1.3}/kryptools.egg-info/top_level.txt +0 -0
  43. {kryptools-1.1 → kryptools-1.3}/setup.cfg +0 -0
  44. {kryptools-1.1 → kryptools-1.3}/tests/test_aes.py +0 -0
  45. {kryptools-1.1 → kryptools-1.3}/tests/test_des.py +0 -0
  46. {kryptools-1.1 → kryptools-1.3}/tests/test_dlog.py +0 -0
  47. {kryptools-1.1 → kryptools-1.3}/tests/test_ec.py +0 -0
  48. {kryptools-1.1 → kryptools-1.3}/tests/test_factor.py +0 -0
  49. {kryptools-1.1 → kryptools-1.3}/tests/test_factor_fmt.py +0 -0
  50. {kryptools-1.1 → kryptools-1.3}/tests/test_factor_pm1.py +0 -0
  51. {kryptools-1.1 → kryptools-1.3}/tests/test_factor_qs.py +0 -0
  52. {kryptools-1.1 → kryptools-1.3}/tests/test_intfuncs.py +0 -0
  53. {kryptools-1.1 → kryptools-1.3}/tests/test_keccak.py +0 -0
  54. {kryptools-1.1 → kryptools-1.3}/tests/test_la.py +0 -0
  55. {kryptools-1.1 → kryptools-1.3}/tests/test_lat.py +0 -0
  56. {kryptools-1.1 → kryptools-1.3}/tests/test_nt.py +0 -0
  57. {kryptools-1.1 → kryptools-1.3}/tests/test_poly.py +0 -0
  58. {kryptools-1.1 → kryptools-1.3}/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.3
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
@@ -39,20 +39,20 @@ The tools contained are:
39
39
  * elliptic curves (Weierstrass form), group operations, order, discrete logarithms
40
40
  * linear algebra: Hermite normal form, Gram-Schmidt
41
41
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
42
+ * linear codes: Hamming distance, left standard form, parity check matrix
42
43
 
43
44
  * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
44
45
  * Poly: a class for polynomials (division, modulo)
45
46
  * Zmod: a class for the ring of integers modulo an integer
46
47
  * GF2: a class for Galois fields GF(2^n)
48
+ * Goppa: a class for encoding and decoding Goppa codes
47
49
 
48
50
  * BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
49
51
  * AESCipher: individual AES operations
50
52
  * DESCipher: individual DES operations
51
53
 
52
- * SHA1
53
- * Keccak individual sponge operations
54
- * SHA3
55
- * SHAKE
54
+ * SHA1: custom initial state, padding function
55
+ * Keccak: individual sponge operations, SHA3, SHAKE
56
56
 
57
57
  Documentation can be found in the jupyter notebook
58
58
  (mostly done, but might not contain everything: use the force, read the source).
@@ -21,20 +21,20 @@ The tools contained are:
21
21
  * elliptic curves (Weierstrass form), group operations, order, discrete logarithms
22
22
  * linear algebra: Hermite normal form, Gram-Schmidt
23
23
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
24
+ * linear codes: Hamming distance, left standard form, parity check matrix
24
25
 
25
26
  * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
26
27
  * Poly: a class for polynomials (division, modulo)
27
28
  * Zmod: a class for the ring of integers modulo an integer
28
29
  * GF2: a class for Galois fields GF(2^n)
30
+ * Goppa: a class for encoding and decoding Goppa codes
29
31
 
30
32
  * BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
31
33
  * AESCipher: individual AES operations
32
34
  * DESCipher: individual DES operations
33
35
 
34
- * SHA1
35
- * Keccak individual sponge operations
36
- * SHA3
37
- * SHAKE
36
+ * SHA1: custom initial state, padding function
37
+ * Keccak: individual sponge operations, SHA3, SHAKE
38
38
 
39
39
  Documentation can be found in the jupyter notebook
40
40
  (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
 
@@ -16,7 +17,7 @@ class Zmod:
16
17
  To define a finite Galois field modulo the prime 5 use
17
18
  >>> Z_5=Zmod(5)
18
19
 
19
- To declare 3 as an element of our Galois filed use
20
+ To declare 3 as an element of our Galois field use
20
21
  >>> Z_5(3)
21
22
  3
22
23
 
@@ -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:
@@ -204,13 +210,17 @@ class ZmodPoint:
204
210
  def __abs__(self) -> int:
205
211
  return abs(self.sharp())
206
212
 
213
+ def bits(self) -> list:
214
+ "Convert to a list of bits."
215
+ return [int(d) for d in str(format(self.x, "0" + str(int.bit_length(self.ring.n - 1)) + "b"))]
216
+
207
217
  def sharp(self):
208
218
  "Returns a symmetric (w.r.t. 0) representative."
209
219
  tmp = (self.ring.n - 1) // 2
210
220
  return (self.x + tmp) % self.ring.n - tmp
211
221
 
212
222
  def order(self) -> int:
213
- """Compute the order of the point in the group Z_n^*."""
223
+ "Compute the order of the point in the group Z_n^*."
214
224
  if self.x == 0 or gcd(self.x, self.ring.n) != 1:
215
225
  raise ValueError(f"{self.x} and {self.ring.n} are not coprime!")
216
226
  order = self.ring.order() # use euler_phi(n) as our current guess
@@ -224,5 +234,5 @@ class ZmodPoint:
224
234
  return order
225
235
 
226
236
  def is_generator(self):
227
- """Test if the point is a generator of the group Z_n^*."""
237
+ "Test if the point is a generator of the group Z_n^*."
228
238
  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.3"
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
@@ -19,6 +19,7 @@ from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round
19
19
  from .poly import Poly
20
20
  from .Zmod import Zmod
21
21
  from .GF2 import GF2, GF2_aes, GF2_miniaes, GF2_ghash
22
+ from .code import left_standard_form, gen2pchk, hamming_dist, Goppa
22
23
  from .blockcipher import BlockCipher, bytexor
23
24
  from .aes import AESKeySchedule, AESCipher, AESBlockCipher
24
25
  from .des import list2int, int2list, permute, invert_permutation, rotate_left, SDESKeySchedule, SDESCipher, DESKeySchedule, DESCipher, DESBlockCipher
@@ -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]
@@ -0,0 +1,124 @@
1
+ """
2
+ Linear codes
3
+ """
4
+
5
+ from .Zmod import Zmod
6
+ from .GF2 import GF2
7
+ from .poly import Poly
8
+ from .la import Matrix, eye, zeros
9
+
10
+
11
+ def poly2bits(x: Poly) -> list:
12
+ "Convert a polynomial in GF(2^n)[x] to a list of binary digits."
13
+ out = []
14
+ for c in x.coeff:
15
+ out += c.bits()
16
+ return out
17
+
18
+ def left_standard_form(M: Matrix) -> (Matrix, Matrix):
19
+ "Compute the left standard form of a generator matrix. Return the standard form and the pertmutation matrix."
20
+ M = M.rref()
21
+ for i in range(M.rows-1,-1,-1):
22
+ if not M[i,:]:
23
+ M.delete_rows(i) # remove zero rows
24
+ last = min(M.rows, M.cols)
25
+ P = eye(M.cols)
26
+ for i in range(last):
27
+ if not M[i,i]:
28
+ j = i + 1
29
+ while j <= last and not M[i, j]:
30
+ j += 1
31
+ if j <= last:
32
+ P[:,i], P[:,j] = P[:,j], P[:,i]
33
+ M[:,i], M[:,j] = M[:,j], M[:,i]
34
+ return M, P
35
+
36
+ def gen2pchk(G: Matrix) -> Matrix:
37
+ "Compute the parity check matrix from a given generator matrix (and vice versa)."
38
+ G, P = left_standard_form(G)
39
+ d = G.cols - G.rows
40
+ assert d > 0
41
+ zero = 0 * G[0]
42
+ H = zeros(d, G.cols, zero = zero)
43
+ for i in range(d):
44
+ H[i,G.rows+i] = zero**0
45
+ tmp = - G[:,-d:].transpose()
46
+ for i in range(G.cols - d):
47
+ H[:, i] = tmp[:, i]
48
+ return H * P.transpose()
49
+
50
+ def hamming_dist(a: iter, b: iter):
51
+ "Hamming distance of two iterables."
52
+ if len(a) != len(b):
53
+ raise ValueError("Both iteralbles must have equal length.")
54
+ return sum(aa != bb for aa, bb in zip(a, b))
55
+
56
+ class Goppa():
57
+ """
58
+ Binary Goppa code.
59
+
60
+ Example:
61
+
62
+ >>> gf = GF2(4) # base field GF(2^n)
63
+ >>> g = Poly([2, 0, 0, 1], ring = gf) # Polynomial for the code
64
+ >>> alpha = list(gf) # list of points from the base field (must not contain zeros of g)
65
+ >>> goppa = Goppa(gf, g, alpha)
66
+
67
+ To encode a list of bits (the length must be equal to the dimension k of the code)
68
+ >>> goppa.encode([1, 0, 1, 1])
69
+ [0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1]
70
+
71
+ Decoding still works if there are atmost deg(g) errors:
72
+ >>> goppa.decode([0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0])
73
+ [1, 0, 1, 1]
74
+
75
+ """
76
+
77
+ def __init__(self, gf: GF2, g: Poly, alpha: list["GF2Point"]):
78
+ assert all(map(g, alpha))
79
+ self.gf = gf
80
+ self.g = g
81
+ self.alpha = alpha
82
+ # Compute the control matrix
83
+ self.Z_2 = Zmod(2)
84
+
85
+ tmp =[]
86
+ for x in alpha:
87
+ tmp.append(Poly([-x, 1], ring = gf, modulus = g).inv().bits())
88
+ self.H = Matrix(tmp, ring=self.Z_2).transpose()
89
+ # Compute the generator matrix
90
+ self.G = gen2pchk(self.H)
91
+
92
+ def encode(self, x: list[int]):
93
+ "Encode a given list of bits."
94
+ x = Matrix(x, ring = self.Z_2)
95
+ y = self.G.transpose() * x
96
+ y.map(int)
97
+ return list(y)
98
+
99
+ def decode(self, y: list[int]):
100
+ "Decode a given list of bits."
101
+ y = Matrix(y, ring = self.Z_2)
102
+ if self.H * y: # no code word
103
+ s = self.gf(0) # syndrom polynomial
104
+ for i, x in enumerate(self.alpha):
105
+ s += int(y[i]) * Poly([-x, 1], ring = self.gf, modulus = self.g).inv()
106
+ v = (s.inv() - Poly([0, 1], ring = self.gf, modulus = self.g))**(self.gf.order ** self.g.degree() // 2)
107
+ # determine error locator polynomial sigma(x) = a(x^2) + b(x^2) * x
108
+ # find a and b by running EEA until b has the desired degree
109
+ v.modulus = None
110
+ r0, r1 = self.g, v
111
+ y0, y1 = Poly([self.gf(0)]), Poly([self.gf(1)])
112
+ while r1 and 2* r1.degree() > self.g.degree():
113
+ q, r = r0.divmod(r1)
114
+ r0, r1 = r1, r
115
+ y0, y1 = y1, y0 - q * y1
116
+ a, b = r1, y1
117
+ sigma = a**2 + b**2 * Poly([0,1], ring = self.gf)
118
+ # correct the error
119
+ for x in self.gf:
120
+ if not sigma(x):
121
+ y[int(x)] += 1
122
+ x = self.G.transpose().solve(y)
123
+ x.map(int)
124
+ return list(x)
@@ -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)