kryptools 1.5.3__tar.gz → 1.6__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 (57) hide show
  1. {kryptools-1.5.3 → kryptools-1.6}/PKG-INFO +3 -2
  2. {kryptools-1.5.3 → kryptools-1.6}/README.md +2 -1
  3. {kryptools-1.5.3 → kryptools-1.6}/kryptools/GF2.py +50 -30
  4. {kryptools-1.5.3 → kryptools-1.6}/kryptools/Zmod.py +8 -5
  5. {kryptools-1.5.3 → kryptools-1.6}/kryptools/__init__.py +2 -2
  6. {kryptools-1.5.3 → kryptools-1.6}/kryptools/code.py +82 -50
  7. {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_qs.py +2 -2
  8. {kryptools-1.5.3 → kryptools-1.6}/kryptools/la.py +54 -2
  9. {kryptools-1.5.3 → kryptools-1.6}/kryptools/poly.py +22 -4
  10. {kryptools-1.5.3 → kryptools-1.6}/kryptools.egg-info/PKG-INFO +3 -2
  11. {kryptools-1.5.3 → kryptools-1.6}/pyproject.toml +1 -1
  12. {kryptools-1.5.3 → kryptools-1.6}/tests/test_GF2.py +12 -1
  13. {kryptools-1.5.3 → kryptools-1.6}/tests/test_Zmod.py +5 -2
  14. {kryptools-1.5.3 → kryptools-1.6}/tests/test_code.py +33 -1
  15. {kryptools-1.5.3 → kryptools-1.6}/tests/test_la.py +14 -1
  16. {kryptools-1.5.3 → kryptools-1.6}/tests/test_poly.py +2 -0
  17. {kryptools-1.5.3 → kryptools-1.6}/LICENSE +0 -0
  18. {kryptools-1.5.3 → kryptools-1.6}/kryptools/aes.py +0 -0
  19. {kryptools-1.5.3 → kryptools-1.6}/kryptools/blockcipher.py +0 -0
  20. {kryptools-1.5.3 → kryptools-1.6}/kryptools/conway_polynomials.py +0 -0
  21. {kryptools-1.5.3 → kryptools-1.6}/kryptools/des.py +0 -0
  22. {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp.py +0 -0
  23. {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp_bsgs.py +0 -0
  24. {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp_ic.py +0 -0
  25. {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp_qs.py +0 -0
  26. {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp_rho.py +0 -0
  27. {kryptools-1.5.3 → kryptools-1.6}/kryptools/ec.py +0 -0
  28. {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor.py +0 -0
  29. {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_dix.py +0 -0
  30. {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_ecm.py +0 -0
  31. {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_fmt.py +0 -0
  32. {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_pm1.py +0 -0
  33. {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_rho.py +0 -0
  34. {kryptools-1.5.3 → kryptools-1.6}/kryptools/intfuncs.py +0 -0
  35. {kryptools-1.5.3 → kryptools-1.6}/kryptools/keccak.py +0 -0
  36. {kryptools-1.5.3 → kryptools-1.6}/kryptools/lat.py +0 -0
  37. {kryptools-1.5.3 → kryptools-1.6}/kryptools/nt.py +0 -0
  38. {kryptools-1.5.3 → kryptools-1.6}/kryptools/primes.py +0 -0
  39. {kryptools-1.5.3 → kryptools-1.6}/kryptools/sha1.py +0 -0
  40. {kryptools-1.5.3 → kryptools-1.6}/kryptools.egg-info/SOURCES.txt +0 -0
  41. {kryptools-1.5.3 → kryptools-1.6}/kryptools.egg-info/dependency_links.txt +0 -0
  42. {kryptools-1.5.3 → kryptools-1.6}/kryptools.egg-info/top_level.txt +0 -0
  43. {kryptools-1.5.3 → kryptools-1.6}/setup.cfg +0 -0
  44. {kryptools-1.5.3 → kryptools-1.6}/tests/test_aes.py +0 -0
  45. {kryptools-1.5.3 → kryptools-1.6}/tests/test_des.py +0 -0
  46. {kryptools-1.5.3 → kryptools-1.6}/tests/test_dlog.py +0 -0
  47. {kryptools-1.5.3 → kryptools-1.6}/tests/test_ec.py +0 -0
  48. {kryptools-1.5.3 → kryptools-1.6}/tests/test_factor.py +0 -0
  49. {kryptools-1.5.3 → kryptools-1.6}/tests/test_factor_fmt.py +0 -0
  50. {kryptools-1.5.3 → kryptools-1.6}/tests/test_factor_pm1.py +0 -0
  51. {kryptools-1.5.3 → kryptools-1.6}/tests/test_factor_qs.py +0 -0
  52. {kryptools-1.5.3 → kryptools-1.6}/tests/test_intfuncs.py +0 -0
  53. {kryptools-1.5.3 → kryptools-1.6}/tests/test_keccak.py +0 -0
  54. {kryptools-1.5.3 → kryptools-1.6}/tests/test_lat.py +0 -0
  55. {kryptools-1.5.3 → kryptools-1.6}/tests/test_nt.py +0 -0
  56. {kryptools-1.5.3 → kryptools-1.6}/tests/test_primes.py +0 -0
  57. {kryptools-1.5.3 → kryptools-1.6}/tests/test_sha1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.5.3
3
+ Version: 1.6
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
@@ -47,13 +47,14 @@ The tools contained are:
47
47
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
48
48
  * linear codes: Hamming distance, left standard form, parity check matrix
49
49
 
50
- * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
50
+ * Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
51
51
  * Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
52
52
  * Zmod: a class for the ring of integers modulo an integer
53
53
  * GF2: a class for Galois fields GF(2^n)
54
54
  * Goppa: a class for encoding and decoding Goppa codes
55
55
  * CyclicCode: a class for encoding and decoding cyclic codes
56
56
  * ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
57
+ * BCHCode: a class for encoding and decoding primitive narrow sense BCH codes
57
58
 
58
59
  * BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
59
60
  * AESCipher: individual AES operations
@@ -29,13 +29,14 @@ The tools contained are:
29
29
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
30
30
  * linear codes: Hamming distance, left standard form, parity check matrix
31
31
 
32
- * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
32
+ * Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
33
33
  * Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
34
34
  * Zmod: a class for the ring of integers modulo an integer
35
35
  * GF2: a class for Galois fields GF(2^n)
36
36
  * Goppa: a class for encoding and decoding Goppa codes
37
37
  * CyclicCode: a class for encoding and decoding cyclic codes
38
38
  * ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
39
+ * BCHCode: a class for encoding and decoding primitive narrow sense BCH codes
39
40
 
40
41
  * BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
41
42
  * AESCipher: individual AES operations
@@ -61,8 +61,12 @@ class GF2:
61
61
 
62
62
  def __call__(self, x: int | list | tuple | range | map):
63
63
  if isinstance(x, list|tuple|range|map):
64
- return [GF2nPoint(xx, self) for xx in x]
65
- return GF2nPoint(x, self)
64
+ if isinstance(x, bytes | bytearray):
65
+ return [ GF2nPoint(int.from_bytes(x, byteorder=self.byteorder), self) for xx in x]
66
+ return [GF2nPoint(int(xx) % self.order, self) for xx in x]
67
+ if isinstance(x, bytes | bytearray):
68
+ return GF2nPoint(int.from_bytes(x, byteorder=self.byteorder), self)
69
+ return GF2nPoint(int(x) % self.order, self)
66
70
 
67
71
  def __len__(self):
68
72
  return self.order
@@ -80,26 +84,26 @@ class GF2:
80
84
  return isinstance(other, GF2nPoint) and self == other.field
81
85
 
82
86
  def _factors(self) -> dict:
83
- "Return the factroization order of the group order of GF(2^n)^*."
87
+ "Return the factroization of the group order of GF(2^n)^*."
84
88
  if not self.factors:
85
89
  self.factors = factorint(self.mult_order)
86
90
  return self.factors
87
91
 
88
92
  def zero(self) -> bool:
89
93
  "Return zero."
90
- return self(0)
94
+ return GF2nPoint(0, self)
91
95
 
92
96
  def one(self) -> bool:
93
97
  "Return one."
94
98
  if self.bitreversed:
95
- return self(self.order - 1)
96
- return self(1)
99
+ return GF2nPoint(self.order >> 1, self)
100
+ return GF2nPoint(1, self)
97
101
 
98
102
  def random(self, num: int = 0) -> "GF2nPoint":
99
103
  "Return a single random point or a list of random points."
100
104
  if num:
101
- return([self(randint(0,self.order-1)) for _ in range(num)])
102
- return self(randint(0,self.order-1))
105
+ return([GF2nPoint(randint(0,self.order-1), self) for _ in range(num)])
106
+ return GF2nPoint(randint(0,self.order-1), self)
103
107
 
104
108
  def is_cyclic(self) -> bool:
105
109
  "GF(2^n)^* is cyclic."
@@ -112,9 +116,9 @@ class GF2:
112
116
  def generator(self) -> int | None:
113
117
  "Return a generator of the group GF(2^n)^*."
114
118
  if self.degree == 1:
115
- return self(1)
119
+ return self.one()
116
120
  for a in range(2, self.order):
117
- a = self(a)
121
+ a = GF2nPoint(a, self)
118
122
  if a.is_generator():
119
123
  return a
120
124
 
@@ -129,16 +133,21 @@ class GF2:
129
133
  def star(self) -> list:
130
134
  "Return a generator for all elements of the group GF(2^n)n^*."
131
135
  for x in range(1, self.order):
132
- yield self(x)
136
+ yield GF2nPoint(x, self)
133
137
 
138
+ def poly(self) -> Poly:
139
+ "Return the associated irreducible polynomial."
140
+ coeff = [int(d) for d in str(
141
+ format(self.modulus, "b"))]
142
+ if self.bitreversed:
143
+ return Poly(coeff + [1])
144
+ return Poly(list(reversed(coeff)))
134
145
 
135
146
  class GF2nPoint:
136
147
  "Represents a point in the Galois field GF(2^n)."
137
148
 
138
149
  def __init__(self, x: int, field: "GF2"):
139
- if isinstance(x, bytes | bytearray):
140
- x = int.from_bytes(x, byteorder=field.byteorder)
141
- self.x = int(x) % field.order
150
+ self.x = x
142
151
  self.field = field
143
152
 
144
153
  def __repr__(self):
@@ -162,13 +171,11 @@ class GF2nPoint:
162
171
 
163
172
  def bits(self) -> list:
164
173
  "Convert to a list of bits."
174
+ coeff = [int(d) for d in str(
175
+ format(self.x, "0" + str(self.field.degree) + "b"))]
165
176
  if self.field.bitreversed:
166
- coeff = [int(d) for d in str(
167
- format(self.x, "0" + str(self.field.degree) + "b"))]
168
- else:
169
- coeff = reversed([int(d) for d in str(
170
- format(self.x, "0" + str(self.field.degree) + "b"))])
171
- return list(coeff)
177
+ return coeff
178
+ return list(reversed(coeff))
172
179
 
173
180
  def poly(self) -> "Poly":
174
181
  "Convert to a polynomial."
@@ -193,7 +200,7 @@ class GF2nPoint:
193
200
  return NotImplemented
194
201
  if self.field != other.field:
195
202
  raise NotImplementedError("Cannot add elements from different fields.")
196
- return self.field(self.x ^ other.x)
203
+ return self.__class__(self.x ^ other.x, self.field)
197
204
 
198
205
  __sub__ = __add__
199
206
 
@@ -206,7 +213,7 @@ class GF2nPoint:
206
213
  if isinstance(other, int):
207
214
  if other % 2:
208
215
  return self
209
- return self.field(0)
216
+ return self.__class__(0, self.field)
210
217
  if not isinstance(other, self.__class__):
211
218
  return NotImplemented
212
219
  if self.field != other.field:
@@ -232,13 +239,13 @@ class GF2nPoint:
232
239
  y <<= 1
233
240
  if y & bitmap1:
234
241
  y ^= self.field.modulus
235
- return self.field(z)
242
+ return self.__class__(z, self.field)
236
243
 
237
244
  def __rmul__(self, scalar: int) -> "GF2nPoint":
238
245
  if isinstance(scalar, int):
239
246
  if scalar % 2:
240
247
  return self
241
- return self.field(0)
248
+ return self.__class__(0, self.field)
242
249
  return NotImplemented
243
250
 
244
251
  def __truediv__(self, other: "GF2nPoint") -> "GF2nPoint":
@@ -255,14 +262,16 @@ class GF2nPoint:
255
262
  return NotImplemented
256
263
  if not (self.x) and j < 0:
257
264
  raise ValueError("Division by zero.")
265
+ j %= self.field.order - 1
258
266
  if self.field.order == 2:
267
+ if j == 0:
268
+ return self.field.one()
259
269
  return self
260
270
  if self.field.bitreversed:
261
- res = self.field(self.field.order >> 1)
271
+ res = self.__class__(self.field.order >> 1, self.field)
262
272
  else:
263
- res = self.field(1)
264
- x = self.field(self.x)
265
- j %= self.field.order - 1
273
+ res = self.__class__(1, self.field)
274
+ x = self.__class__(self.x, self.field)
266
275
  while j > 0:
267
276
  # If j is odd, multiply with x
268
277
  if j & 1:
@@ -276,13 +285,13 @@ class GF2nPoint:
276
285
  "Cyclic rotation to the left."
277
286
  x = self.x << j
278
287
  x = (x % self.field.order) + (x // self.field.order)
279
- return self.field(x)
288
+ return self.__class__(self.x, self.field)
280
289
 
281
290
  def __rshift__(self, j: int) -> "GF2nPoint":
282
291
  "Cyclic rotation to the right."
283
292
  x = self.x >> j
284
293
  x += (self.field.order >> j) * (self.x % 2**j)
285
- return self.field(x)
294
+ return self.__class__(self.x, self.field)
286
295
 
287
296
  def sqrt(self) -> "GF2nPoint":
288
297
  "Compute the square root."
@@ -308,6 +317,17 @@ class GF2nPoint:
308
317
  "Test if the point is a generator of the group GF(2^n)^*."
309
318
  return self.field.mult_order == self.order()
310
319
 
320
+ def minpoly(self) -> Poly:
321
+ "Return the minimal polynomial of an element."
322
+ aj = self
323
+ mpoly = Poly([aj, self.field.one()])
324
+ for _ in range(1, self.field.degree):
325
+ aj = aj**2 # Frobenius
326
+ if aj == self:
327
+ break
328
+ mpoly *= Poly([aj, self.field.one()])
329
+ return mpoly
330
+
311
331
  def sbox(self, inv: bool = False) -> "GF2nPoint":
312
332
  "Apply the AES sbox."
313
333
  if self.field.degree == 8 and self.field.modulus == 0b100011011: # AES
@@ -43,8 +43,8 @@ class Zmod:
43
43
 
44
44
  def __call__(self, x: int | list | tuple | range | map):
45
45
  if isinstance(x, list|tuple|range|map):
46
- return [ZmodPoint(xx, self) for xx in x]
47
- return ZmodPoint(x, self)
46
+ return [ZmodPoint(int(xx), self) for xx in x]
47
+ return ZmodPoint(int(x), self)
48
48
 
49
49
  def __iter__(self):
50
50
  for x in range(self.n):
@@ -157,7 +157,7 @@ class ZmodPoint:
157
157
  "Represents a point in the ring Zmod."
158
158
 
159
159
  def __init__(self, x: int, ring: "Zmod"):
160
- self.x = int(x) % ring.n
160
+ self.x = x % ring.n
161
161
  self.ring = ring
162
162
 
163
163
  def __repr__(self):
@@ -247,8 +247,11 @@ class ZmodPoint:
247
247
 
248
248
  def sharp(self):
249
249
  "Returns a symmetric (w.r.t. 0) representative."
250
- tmp = (self.ring.n - 1) // 2
251
- return (self.x + tmp) % self.ring.n - tmp
250
+ if self.x <= self.ring.n // 2:
251
+ return self.x
252
+ return self.x - self.ring.n
253
+
254
+ mods = sharp
252
255
 
253
256
  def order(self) -> int:
254
257
  "Compute the order of the point in the group Z_n^*."
@@ -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.5.3"
8
+ __version__ = "1.6"
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,7 +19,7 @@ from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round
19
19
  from .poly import Poly, lagrange_interpolation
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, CyclicCode, ReedSolomonCode
22
+ from .code import gen2pchk, hamming_dist, Goppa, CyclicCode, ReedSolomonCode, BCHCode
23
23
  from .blockcipher import BlockCipher, bytexor
24
24
  from .aes import AESKeySchedule, AESCipher, AESBlockCipher
25
25
  from .des import list2int, int2list, permute, invert_permutation, rotate_left, SDESKeySchedule, SDESCipher, DESKeySchedule, DESCipher, DESBlockCipher
@@ -5,8 +5,8 @@ Linear codes
5
5
  from math import prod
6
6
  from .Zmod import Zmod
7
7
  from .GF2 import GF2
8
- from .poly import Poly, lagrange_interpolation
9
- from .la import Matrix, eye, zeros
8
+ from .poly import Poly
9
+ from .la import Matrix
10
10
 
11
11
 
12
12
  def poly2bits(x: Poly) -> list:
@@ -16,45 +16,13 @@ def poly2bits(x: Poly) -> list:
16
16
  out += c.bits()
17
17
  return out
18
18
 
19
- def left_standard_form(M: Matrix) -> (Matrix, Matrix):
19
+ def left_standard_form(G: Matrix) -> (Matrix, Matrix):
20
20
  "Compute the left standard form of a generator matrix. Return the standard form and the pertmutation matrix."
21
- # reduced row echelon form
22
- M = M.rref()
23
- # purge zero rows
24
- for i in range(M.rows-1,-1,-1):
25
- if not M[i,:]:
26
- M.delete_rows(i)
27
- # permute columns to get the identity on the left
28
- last = min(M.rows, M.cols)
29
- P = eye(M.cols)
30
- for i in range(last):
31
- if not M[i,i]: # the diagonal entry vanishes
32
- # find the index of the pivot and permute
33
- j = i + 1
34
- while j <= M.cols and not M[i, j]:
35
- j += 1
36
- P[:,i], P[:,j] = P[:,j], P[:,i]
37
- M[:,i], M[:,j] = M[:,j], M[:,i]
38
- return M, P
21
+ return G.left_standard_form()
39
22
 
40
23
  def gen2pchk(G: Matrix) -> Matrix:
41
24
  "Compute the parity check matrix from a given generator matrix (and vice versa)."
42
- G, P = left_standard_form(G)
43
- d = G.cols - G.rows
44
- if d <= 0:
45
- raise ValueError("A generator matrix must have more columns than rows!")
46
- zero = 0 * G[0]
47
- one = zero**0
48
- # we start with a zero matrix
49
- H = zeros(d, G.cols, zero = zero)
50
- # create the identity on the right
51
- for i in range(d):
52
- H[i,G.rows+i] = one
53
- # add minus the transpose of the right part of G as the left part
54
- tmp = - G[:,-d:].transpose()
55
- for i in range(G.cols - d):
56
- H[:, i] = tmp[:, i]
57
- return H * P.transpose()
25
+ return G.kernel().transpose()
58
26
 
59
27
  def hamming_dist(a: iter, b: iter):
60
28
  "Hamming distance of two iterables."
@@ -217,13 +185,18 @@ class CyclicCode():
217
185
  raise ValueError(f"Degree can be at most {self.n-1}.")
218
186
  x, r = y.divmod(self.g)
219
187
  if r:
220
- raise NotImplementedError("Word is no code word! Cannot decode.")
188
+ y = self.correct(y)
189
+ x, r = y.divmod(self.g)
221
190
  if systematic_form:
222
191
  x.coeff = y.coeff[self.g.degree():]
223
192
  if as_list:
224
193
  return x.coeff + [zero] * (self.k - len(x.coeff))
225
194
  return x
226
195
 
196
+ def correct(self, y: Poly):
197
+ "Correct a given polynomial."
198
+ raise NotImplementedError("Error correction not implemented.")
199
+
227
200
  def generator_matrix(self):
228
201
  "Return the generator matrix of the code."
229
202
  zero = 0 * self.g[0]
@@ -249,7 +222,7 @@ class ReedSolomonCode(CyclicCode):
249
222
  >>> rsc.encode([1, 2, 3])
250
223
  [6, 4, 5, 1, 8, 4, 2, 9, 2, 8, 9, 6]
251
224
 
252
- To decoding use:
225
+ To decode use:
253
226
  >>> rsc.decode([1, 4, 4, 0, 8, 4, 2, 9, 2, 0, 9, 6])
254
227
  [1, 2, 3]
255
228
 
@@ -265,7 +238,9 @@ class ReedSolomonCode(CyclicCode):
265
238
  self.order = n + 1 # order of the base field
266
239
  self.n = n # lenght
267
240
  self.k = k # dimension
268
- self.d = n - k - 1 # minimal distance
241
+ if k >= n:
242
+ raise ValueError(f"Code dimension k = {k} must be smaller than the code lenght n = {self.n}.")
243
+ self.d = n - k + 1 # minimal distance
269
244
  self.modulus = Poly([-one] + [zero] * (n-1) + [one])
270
245
  if alpha is None:
271
246
  if hasattr(gf, "primitive_element"):
@@ -309,19 +284,76 @@ class ReedSolomonCode(CyclicCode):
309
284
  x = Poly(xx)
310
285
  if x.degree() >= self.k: # No codewort
311
286
  t = (self.n - self.k) // 2
287
+ if not t:
288
+ raise ValueError("This code cannot correct any errors.")
312
289
  A = Matrix([[ xx[j - l % self.n] for l in range(1, t+1) ] for j in range(self.k+t, self.k+2*t) ])
313
290
  b = [ -xx[self.k + t + l ] for l in range(t) ]
314
291
  lam = Poly([one] + list(A.solve(b)))
315
- x_coordinates = []
316
- y_coordinates = []
317
- for j in range(self.n):
318
- if len(x_coordinates) == self.k:
319
- break
320
- alphaj = self.alpha**j
321
- if lam(alphaj):
322
- x_coordinates.append(alphaj)
323
- y_coordinates.append(y[j])
324
- x = lagrange_interpolation(x_coordinates, y_coordinates)
292
+ lamp = lam.derivative()
293
+ s = Poly([x[self.k + j] for j in range(self.n - self.k)])
294
+ omega = s * lam
295
+ omega = Poly(omega.coeff[:self.n - self.k]) # mod x^(n-k)
296
+ # Chien search
297
+ lam = lam.coeff
298
+ alphai = [ self.alpha**i for i in range(len(lam)) ]
299
+ for i in range(self.n):
300
+ if not sum(lam, start = zero):
301
+ # Fornay
302
+ ali = self.alpha**i
303
+ y[i] -= ali**(self.k-1) * omega(ali) / lamp(ali)
304
+ for j in range(len(lam)): # pylint: disable=C0200
305
+ lam[j] *= alphai[j]
306
+ x = Poly([ -y(self.alpha**(-j)) for j in range(self.n) ])
325
307
  if as_list:
326
308
  return x.coeff + [zero] * (self.k - len(x.coeff))
327
309
  return x
310
+
311
+ def correct(self, y: Poly):
312
+ "Correct a given polynomial."
313
+ x = self.decode(y)
314
+ return self.encode(x)
315
+
316
+ class BCHCode(CyclicCode):
317
+ """
318
+ Binary narrow sense primitive BCH code.
319
+
320
+ Example:
321
+
322
+ >>> n = 7 # lenght of the code
323
+ >>> D = 2 # desired distance
324
+ >>> bch = BCHCode(k, D)
325
+
326
+ To encode a list of letters (the length must be equal to the dimension k of the code; conversion to the Galois field is done automatically):
327
+ >>> bch.encode([0, 1, 0, 1])
328
+ [0, 1, 1, 1, 0, 0, 1]
329
+
330
+ To decode use:
331
+ >>> bch.decode([0, 1, 1, 1, 0, 0, 1])
332
+ [0, 1, 0, 1]
333
+
334
+ """
335
+ def __init__(self, n:int, D: int):
336
+ m = n.bit_length()
337
+ if 2**m - 1 != n:
338
+ raise ValueError(f"The codelenght n={n} plus one must be a power of 2.")
339
+ self.rsc = ReedSolomonCode(n - D + 1, GF2(m))
340
+ # Compute the generator polynomial
341
+ aj = self.rsc.alpha
342
+ g = aj.minpoly()
343
+ for _ in range(2,D):
344
+ aj *= self.rsc.alpha
345
+ mj = aj.minpoly()
346
+ g = g.lcm(mj)
347
+ g.map(GF2(1))
348
+ super().__init__(n, g)
349
+
350
+ def __repr__(self):
351
+ return f"BCH [{self.n}, {self.k}] code over GF({self.order}) with generator polynomial g(x) = {self.g}."
352
+
353
+ def correct(self, y: Poly):
354
+ "Correct a given polynomial."
355
+ y.map(self.rsc.gf)
356
+ x = self.rsc.decode(y)
357
+ y = self.rsc.encode(x)
358
+ y.map(GF2(1))
359
+ return y
@@ -13,13 +13,13 @@ def bytexor(a: bytearray, b: bytes) -> bytes:
13
13
  a[i] ^= b[i]
14
14
 
15
15
 
16
- def byteset(a: bytearray, i: int) -> bytes:
16
+ def byteset(a: bytearray, i: int) -> None:
17
17
  """Set the i'th bit in the bytearray `a` to 1."""
18
18
  i1, i0 = divmod(i, 8)
19
19
  a[i1] |= 2**i0
20
20
 
21
21
 
22
- def bytetest(a: bytes, i: int) -> bytes:
22
+ def bytetest(a: bytes, i: int) -> bool:
23
23
  """Test if the i'th bit in the bytestring `a` is 1."""
24
24
  i1, i0 = divmod(i, 8)
25
25
  return (a[i1] & 2**i0) != 0
@@ -169,7 +169,9 @@ class Matrix:
169
169
 
170
170
  def append_row(self, row: list|tuple, ring = None) -> None:
171
171
  "Append a row."
172
- if not isinstance(row, list|tuple) or len(row) != self.cols:
172
+ if not isinstance(row, list|tuple):
173
+ row = list(row)
174
+ if len(row) != self.cols:
173
175
  raise ValueError("Length does not match the number of columns.")
174
176
  if ring:
175
177
  row =list(map(ring, row))
@@ -178,7 +180,9 @@ class Matrix:
178
180
 
179
181
  def append_column(self, col: list|tuple, ring = None) -> None:
180
182
  "Append a column."
181
- if not isinstance(col, list|tuple) or len(col) != self.rows:
183
+ if not isinstance(col, list|tuple):
184
+ col = list(col)
185
+ if len(col) != self.rows:
182
186
  raise ValueError("Length does not match the number of rows.")
183
187
  if ring:
184
188
  col =list(map(ring, col))
@@ -312,6 +316,50 @@ class Matrix:
312
316
  break
313
317
  return R
314
318
 
319
+ def left_standard_form(self) -> ("Matrix", "Matrix"):
320
+ "Compute the left standard form of a matrix. Return the standard form and the pertmutation matrix."
321
+ # reduced row echelon form
322
+ M = self.rref()
323
+ # purge zero rows
324
+ for i in range(M.rows-1,-1,-1):
325
+ if not M[i,:]:
326
+ M.delete_rows(i)
327
+ # permute columns to get the identity on the left
328
+ last = min(M.rows, M.cols)
329
+ P = M.eye(M.cols)
330
+ for i in range(last):
331
+ if not M[i,i]: # the diagonal entry vanishes
332
+ # find the index of the pivot and permute
333
+ j = i + 1
334
+ while j <= M.cols and not M[i, j]:
335
+ j += 1
336
+ P[:,i], P[:,j] = P[:,j], P[:,i]
337
+ M[:,i], M[:,j] = M[:,j], M[:,i]
338
+ return M, P
339
+
340
+ def kernel(self) -> "Matrix":
341
+ "Compute a basis for the kernel of a matrix."
342
+ _, one = self._guess_zero()
343
+ M = self.rref()
344
+ pivotcols = []
345
+ nonpivotcols = []
346
+ j = 0
347
+ for i in range(0, M.rows):
348
+ while j < M.cols and not M[i,j]:
349
+ nonpivotcols.append(j)
350
+ j += 1
351
+ if j == M.cols:
352
+ break
353
+ pivotcols.append(j)
354
+ j += 1
355
+ nonpivotcols += list(range(j, M.cols))
356
+ K = M.zeros(M.cols, max(1,len(nonpivotcols)))
357
+ for k, j in enumerate(nonpivotcols):
358
+ K[j, k] = one
359
+ for l, i in enumerate(pivotcols):
360
+ K[i, k] = - M[l, j]
361
+ return K
362
+
315
363
  def det(self) -> int:
316
364
  "Compute the determinant of a matrix M."
317
365
  zero, one = self._guess_zero()
@@ -349,6 +397,10 @@ class Matrix:
349
397
  return i+1
350
398
  return 0
351
399
 
400
+ def nullity(self) -> int:
401
+ "Compute the nillity of a matrix M."
402
+ return self.cols - self.rank()
403
+
352
404
  def inv(self) -> "Matrix":
353
405
  "Compute the inverse of a square matrix M."
354
406
  if self.rows != self.cols:
@@ -18,6 +18,7 @@ class Poly:
18
18
  """
19
19
 
20
20
  print_reversed = True # print the terms in reversed order
21
+ print_latex = False # print in latex format
21
22
  print_x = "x" # variable for printing
22
23
  print_pow = "^" # you can change this to "**" if you want it python style
23
24
 
@@ -63,16 +64,20 @@ class Poly:
63
64
  def __hash__(self):
64
65
  return hash(tuple(self.coeff))
65
66
 
66
- def __repr__(self, latex=False):
67
+ def __repr__(self, latex=None):
67
68
  def prx(i: int) -> str:
68
69
  if i == 0:
69
70
  return ""
70
71
  if i == 1:
71
72
  return self.__class__.print_x
72
73
  if latex:
74
+ if i < 10:
75
+ return self.__class__.print_x + "^" + str(i)
73
76
  return self.__class__.print_x + "^{" + str(i) + "}"
74
77
  return self.__class__.print_x + self.__class__.print_pow + str(i)
75
78
 
79
+ if latex is None:
80
+ latex = self.__class__.print_latex
76
81
  if not self:
77
82
  return str(self.coeff[0])
78
83
  one = self.coeff[0]**0
@@ -298,7 +303,7 @@ class Poly:
298
303
  return self.__class__([zero]), self
299
304
  div = [zero] * (sd - od + 1)
300
305
  lco = other.coeff[-1]
301
- if bool(lco - one):
306
+ if lco != one:
302
307
  tmp = one / lco
303
308
  oth = [c * tmp for c in other.coeff]
304
309
  rem = [c * tmp for c in self.coeff]
@@ -310,7 +315,7 @@ class Poly:
310
315
  div[sd - od - i] = tmp
311
316
  for j in range(od + 1):
312
317
  rem[sd - i - j] -= tmp * oth[od - j]
313
- if bool(lco - one):
318
+ if lco != one:
314
319
  rem = [c * lco for c in rem]
315
320
  return self.__class__(div, modulus=self.modulus), self.__class__(rem, modulus=self.modulus)
316
321
 
@@ -327,7 +332,7 @@ class Poly:
327
332
  if sd < od:
328
333
  return self
329
334
  lco = other.coeff[-1]
330
- if bool(lco - one):
335
+ if lco != one:
331
336
  tmp = one / lco
332
337
  oth = [c * tmp for c in other.coeff]
333
338
  else:
@@ -363,12 +368,15 @@ class Poly:
363
368
  if r0.degree() != 0:
364
369
  raise ValueError(f"{self} is not invertible mod {other}.")
365
370
  y0.modulus = self.modulus
371
+ if r0.coeff[0] == one:
372
+ return y0
366
373
  return y0 / r0.coeff[0]
367
374
 
368
375
  def gcd(self, other: "Poly") -> "Poly":
369
376
  "Greates common divisor with a given polynomial."
370
377
  if not isinstance(other, self.__class__):
371
378
  raise NotImplementedError(f"Cannot compute gcd: {other} must be a polynomial.")
379
+ _, one, _ = self._guess_ring()
372
380
  if not self:
373
381
  if not other:
374
382
  return Poly([self.coeff[0]])
@@ -379,8 +387,14 @@ class Poly:
379
387
  while r1:
380
388
  r0, r1 = r1, r0.divmod(r1)[1]
381
389
  r0.modulus = None
390
+ if r0.coeff[-1] == one:
391
+ return r0
382
392
  return r0 / r0.coeff[-1]
383
393
 
394
+ def lcm(self, other: "Poly") -> "Poly":
395
+ "Least common multiple with a given polynomial."
396
+ return self * other // self.gcd(other)
397
+
384
398
  def egcd(self, other: "Poly") -> ("Poly", "Poly", "Poly"):
385
399
  """Perform the extended Euclidean agorithm for polynomials. Returns gcd, x, y such that other * x + self * y = gcd."""
386
400
  zero, one, _ = self._guess_ring()
@@ -389,6 +403,8 @@ class Poly:
389
403
  if not self:
390
404
  if not other:
391
405
  return Poly([zero]), Poly([zero]), Poly([zero])
406
+ if other.coeff[-1] == one:
407
+ return other, Poly([zero]), one
392
408
  return other / other.coeff[-1], Poly([zero]), one / other.coeff[-1]
393
409
  r0, r1 = other, self
394
410
  x0, x1 = self.__class__([one]), self.__class__([zero])
@@ -399,6 +415,8 @@ class Poly:
399
415
  x0, x1 = x1, x0 - x1 * q
400
416
  y0, y1 = y1, y0 - y1 * q
401
417
  lcr0 = r0.coeff[-1]
418
+ if lcr0 == one:
419
+ return r0, x0, y0
402
420
  return r0 / lcr0, x0 / lcr0, y0 / lcr0
403
421
 
404
422
  def derivative(self) -> "Poly":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.5.3
3
+ Version: 1.6
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
@@ -47,13 +47,14 @@ The tools contained are:
47
47
  * lattices: Hadamard ratio, Babai rounding/nearest plane algorithm, lattice reduction (Lenstra-Lenstra-Lovaz)
48
48
  * linear codes: Hamming distance, left standard form, parity check matrix
49
49
 
50
- * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
50
+ * Matrix: a class for Matrices (inverse, det, reduced echelon form, kernel, etc.)
51
51
  * Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
52
52
  * Zmod: a class for the ring of integers modulo an integer
53
53
  * GF2: a class for Galois fields GF(2^n)
54
54
  * Goppa: a class for encoding and decoding Goppa codes
55
55
  * CyclicCode: a class for encoding and decoding cyclic codes
56
56
  * ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
57
+ * BCHCode: a class for encoding and decoding primitive narrow sense BCH codes
57
58
 
58
59
  * BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
59
60
  * AESCipher: individual AES operations
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "1.5.3"
3
+ version = "1.6"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
@@ -4,7 +4,7 @@ from random import randint, seed # pylint: disable=C0411
4
4
  from kryptools import GF2, euler_phi
5
5
  seed(0)
6
6
 
7
- num_tests = 50
7
+ num_tests = 25
8
8
 
9
9
  def test_GF2_ops():
10
10
  for n, modulus in [(4, None), (8, None), (8, 0b100011011), (12, None), (128, 0b11100001 << 120)]:
@@ -14,6 +14,10 @@ def test_GF2_ops():
14
14
  assert gf(1) == gf(1)
15
15
  assert gf(1) != gf(0)
16
16
  assert gf(0) != 0
17
+ assert 0 * gf.one() == gf.zero()
18
+ assert 1 * gf.one() == gf.one()
19
+ assert 2 * gf.one() == gf.zero()
20
+ assert gf.zero()**0 == gf.one()
17
21
  if gf.degree < 17:
18
22
  assert len(list(gf)) == gf.order
19
23
  assert len(list(gf.star())) == gf.order - 1
@@ -21,9 +25,16 @@ def test_GF2_ops():
21
25
  for _ in range(num_tests):
22
26
  a = gf(randint(0, gf.order-1))
23
27
  b = gf(randint(0, gf.order-1))
28
+ assert a * gf.one() == a
29
+ assert gf.one() * a == a
30
+ assert a**0 == gf.one()
31
+ assert a**1 == a
32
+ assert a**2 == a * a
33
+ assert a * b == b * a
24
34
  assert (a + b).poly() == a.poly() + b.poly()
25
35
  assert (a - b).poly() == a.poly() - b.poly()
26
36
  assert (a * b).poly() == a.poly() * b.poly()
37
+ assert (a**2).poly() == a.poly()**2
27
38
  if a:
28
39
  assert (a**-1).poly() == a.poly().inv()
29
40
  if b:
@@ -41,8 +41,8 @@ def test_Zmod_methods():
41
41
  assert Z_5(3).is_generator() is True
42
42
  assert list(Z_5.generators()) == Z_5([2, 3])
43
43
  assert list(Z_5.star()) == Z_5([1, 2, 3, 4])
44
- assert [Z_5(i).sharp() for i in range(Z_5.n)] == [0, 1, 2, -2, -1]
45
- assert [abs(Z_5(i)) for i in range(Z_5.n)] == [0, 1, 2, 2, 1]
44
+ assert [i.sharp() for i in Z_5] == [0, 1, 2, -2, -1]
45
+ assert [abs(i) for i in Z_5] == [0, 1, 2, 2, 1]
46
46
  assert not Z_5(0)
47
47
  assert Z_5(1)
48
48
  assert Z_5(2) == Z_5(2)
@@ -63,6 +63,9 @@ def test_Zmod_methods():
63
63
  assert Z_6(5).is_generator() is True
64
64
  assert list(Z_6.generators()) == Z_6([5])
65
65
  assert list(Z_6.star()) == Z_6([1, 5])
66
+ assert [i.sharp() for i in Z_6] == [0, 1, 2, 3, -2, -1]
67
+ assert [abs(i) for i in Z_6] == [0, 1, 2, 3, 2, 1]
68
+ assert not Z_6(0)
66
69
  assert Z_6(1)
67
70
  assert Z_6(2) == Z_6(2)
68
71
  assert Z_6(2) != Z_6(3)
@@ -2,7 +2,7 @@
2
2
  import pytest # pylint: disable=W0611
3
3
  from random import randint, seed # pylint: disable=C0411
4
4
  from kryptools import Zmod, GF2, Poly, Matrix
5
- from kryptools import gen2pchk, Goppa, CyclicCode, ReedSolomonCode
5
+ from kryptools import gen2pchk, Goppa, CyclicCode, ReedSolomonCode, BCHCode
6
6
 
7
7
  seed(0)
8
8
  Z_2 = Zmod(2)
@@ -33,6 +33,7 @@ def test_Goppa():
33
33
  alpha = list(gf)
34
34
 
35
35
  goppa = Goppa(gf, g, alpha)
36
+ assert not goppa.H * goppa.G.transpose()
36
37
  for _ in range(num_tests):
37
38
  x = [ randint(0,1) for _ in range(goppa.G.rows) ]
38
39
  y = goppa.encode(x)
@@ -45,6 +46,12 @@ def test_Cyclic():
45
46
  for gf, n, g in [ [Zmod(2), 4, [1, 1]], [Zmod(3), 4, [-1, 1]], [Zmod(11), 5, [4, 6, 1]] ]:
46
47
  g = Poly(g, ring = gf)
47
48
  cc = CyclicCode(n, g)
49
+ G = cc.generator_matrix()
50
+ H = cc.check_matrix()
51
+ assert not H * G.transpose()
52
+ H2 = gen2pchk(G)
53
+ assert H2.rows == H.rows
54
+ assert not H2 * G.transpose()
48
55
  for _ in range(num_tests):
49
56
  x = [ gf(randint(0, cc.order-1)) for _ in range(cc.k) ]
50
57
  y = cc.encode(x)
@@ -53,6 +60,12 @@ def test_Cyclic():
53
60
  def test_ReedSolomon():
54
61
  for gf, k in [ [Zmod(13), 3], [GF2(6), 4]]:
55
62
  rsc = ReedSolomonCode(k, gf)
63
+ G = rsc.generator_matrix()
64
+ H = rsc.check_matrix()
65
+ assert not H * G.transpose()
66
+ H2 = gen2pchk(G)
67
+ assert H2.rows == H.rows
68
+ assert not H2 * G.transpose()
56
69
  t = (rsc.n - rsc.k) // 2
57
70
  for _ in range(num_tests):
58
71
  x = [ gf(randint(0, rsc.n)) for _ in range(rsc.k) ]
@@ -61,3 +74,22 @@ def test_ReedSolomon():
61
74
  i = randint(0, rsc.n-1)
62
75
  y[i] += gf(1)
63
76
  assert rsc.decode(y) == x
77
+
78
+ def test_BCH():
79
+ gf = GF2(1)
80
+ for n, D in [ [7, 3], [15, 5]]:
81
+ bch = BCHCode(n, D)
82
+ G = bch.generator_matrix()
83
+ H = bch.check_matrix()
84
+ assert not H * G.transpose()
85
+ H2 = gen2pchk(G)
86
+ assert H2.rows == H.rows
87
+ assert not H2 * G.transpose()
88
+ t = (D - 1) // 2
89
+ for _ in range(num_tests):
90
+ x = [ gf(randint(0, 1)) for _ in range(bch.k) ]
91
+ y = bch.encode(x)
92
+ for _ in range(t):
93
+ i = randint(0, bch.n-1)
94
+ y[i] += gf(1)
95
+ assert bch.decode(y) == x
@@ -1,7 +1,7 @@
1
1
  # pragma pylint: disable=C0114,C0116
2
2
  import pytest # pylint: disable=W0611
3
3
  from fractions import Fraction # pylint: disable=C0411
4
- from kryptools import Matrix, Zmod
4
+ from kryptools import Matrix, Zmod, GF2
5
5
 
6
6
 
7
7
  def test_Matrix():
@@ -26,3 +26,16 @@ def test_Matrix():
26
26
  M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 12]], ring=Z_11)
27
27
  Mi = M.inv()
28
28
  assert M * Mi == M.eye()
29
+
30
+ def test_kernel():
31
+ for gf in Zmod(2), Zmod(7), GF2(1), GF2(8):
32
+ for m,n in ((3, 5), (5, 3), (3, 3)):
33
+ for _ in range(25):
34
+ A = Matrix([[gf.random() for _ in range(m)] for _ in range(n)], ring = gf)
35
+ K = A.kernel()
36
+ assert not A * K
37
+ assert K.rows == A.cols
38
+ if K:
39
+ assert K.cols == A.nullity()
40
+ else:
41
+ assert K.cols == 1
@@ -18,6 +18,7 @@ def test_Poly():
18
18
  assert q.degree() == 3
19
19
  assert q.weight() == 4
20
20
  d, m = q.divmod(p)
21
+ assert q * p == p.gcd(q) * p.lcm(q)
21
22
  assert d * p + m == q
22
23
  for _ in range(num_tests):
23
24
  p = Poly([randint(0,100) for _ in range(10)])
@@ -33,6 +34,7 @@ def test_Poly():
33
34
  q = Poly([1, 2, 3, 4], ring = gf)
34
35
  d, m = q.divmod(p)
35
36
  assert d * p + m == q
37
+ assert q * p == p.gcd(q) * p.lcm(q)
36
38
  for _ in range(num_tests):
37
39
  p = Poly(gf.random(10))
38
40
  q = Poly(gf.random(12))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes