kryptools 1.5.2__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.2 → kryptools-1.6}/PKG-INFO +3 -2
  2. {kryptools-1.5.2 → kryptools-1.6}/README.md +2 -1
  3. {kryptools-1.5.2 → kryptools-1.6}/kryptools/GF2.py +52 -38
  4. {kryptools-1.5.2 → kryptools-1.6}/kryptools/Zmod.py +8 -5
  5. {kryptools-1.5.2 → kryptools-1.6}/kryptools/__init__.py +2 -2
  6. {kryptools-1.5.2 → kryptools-1.6}/kryptools/code.py +90 -58
  7. {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_qs.py +2 -2
  8. {kryptools-1.5.2 → kryptools-1.6}/kryptools/la.py +54 -2
  9. {kryptools-1.5.2 → kryptools-1.6}/kryptools/poly.py +26 -4
  10. {kryptools-1.5.2 → kryptools-1.6}/kryptools.egg-info/PKG-INFO +3 -2
  11. {kryptools-1.5.2 → kryptools-1.6}/pyproject.toml +1 -1
  12. {kryptools-1.5.2 → kryptools-1.6}/tests/test_GF2.py +12 -1
  13. {kryptools-1.5.2 → kryptools-1.6}/tests/test_Zmod.py +5 -2
  14. {kryptools-1.5.2 → kryptools-1.6}/tests/test_code.py +33 -1
  15. {kryptools-1.5.2 → kryptools-1.6}/tests/test_la.py +14 -1
  16. {kryptools-1.5.2 → kryptools-1.6}/tests/test_poly.py +9 -0
  17. {kryptools-1.5.2 → kryptools-1.6}/LICENSE +0 -0
  18. {kryptools-1.5.2 → kryptools-1.6}/kryptools/aes.py +0 -0
  19. {kryptools-1.5.2 → kryptools-1.6}/kryptools/blockcipher.py +0 -0
  20. {kryptools-1.5.2 → kryptools-1.6}/kryptools/conway_polynomials.py +0 -0
  21. {kryptools-1.5.2 → kryptools-1.6}/kryptools/des.py +0 -0
  22. {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp.py +0 -0
  23. {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp_bsgs.py +0 -0
  24. {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp_ic.py +0 -0
  25. {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp_qs.py +0 -0
  26. {kryptools-1.5.2 → kryptools-1.6}/kryptools/dlp_rho.py +0 -0
  27. {kryptools-1.5.2 → kryptools-1.6}/kryptools/ec.py +0 -0
  28. {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor.py +0 -0
  29. {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_dix.py +0 -0
  30. {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_ecm.py +0 -0
  31. {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_fmt.py +0 -0
  32. {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_pm1.py +0 -0
  33. {kryptools-1.5.2 → kryptools-1.6}/kryptools/factor_rho.py +0 -0
  34. {kryptools-1.5.2 → kryptools-1.6}/kryptools/intfuncs.py +0 -0
  35. {kryptools-1.5.2 → kryptools-1.6}/kryptools/keccak.py +0 -0
  36. {kryptools-1.5.2 → kryptools-1.6}/kryptools/lat.py +0 -0
  37. {kryptools-1.5.2 → kryptools-1.6}/kryptools/nt.py +0 -0
  38. {kryptools-1.5.2 → kryptools-1.6}/kryptools/primes.py +0 -0
  39. {kryptools-1.5.2 → kryptools-1.6}/kryptools/sha1.py +0 -0
  40. {kryptools-1.5.2 → kryptools-1.6}/kryptools.egg-info/SOURCES.txt +0 -0
  41. {kryptools-1.5.2 → kryptools-1.6}/kryptools.egg-info/dependency_links.txt +0 -0
  42. {kryptools-1.5.2 → kryptools-1.6}/kryptools.egg-info/top_level.txt +0 -0
  43. {kryptools-1.5.2 → kryptools-1.6}/setup.cfg +0 -0
  44. {kryptools-1.5.2 → kryptools-1.6}/tests/test_aes.py +0 -0
  45. {kryptools-1.5.2 → kryptools-1.6}/tests/test_des.py +0 -0
  46. {kryptools-1.5.2 → kryptools-1.6}/tests/test_dlog.py +0 -0
  47. {kryptools-1.5.2 → kryptools-1.6}/tests/test_ec.py +0 -0
  48. {kryptools-1.5.2 → kryptools-1.6}/tests/test_factor.py +0 -0
  49. {kryptools-1.5.2 → kryptools-1.6}/tests/test_factor_fmt.py +0 -0
  50. {kryptools-1.5.2 → kryptools-1.6}/tests/test_factor_pm1.py +0 -0
  51. {kryptools-1.5.2 → kryptools-1.6}/tests/test_factor_qs.py +0 -0
  52. {kryptools-1.5.2 → kryptools-1.6}/tests/test_intfuncs.py +0 -0
  53. {kryptools-1.5.2 → kryptools-1.6}/tests/test_keccak.py +0 -0
  54. {kryptools-1.5.2 → kryptools-1.6}/tests/test_lat.py +0 -0
  55. {kryptools-1.5.2 → kryptools-1.6}/tests/test_nt.py +0 -0
  56. {kryptools-1.5.2 → kryptools-1.6}/tests/test_primes.py +0 -0
  57. {kryptools-1.5.2 → 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.2
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,26 +200,20 @@ 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
- def __neg__(self) -> "GF2nPoint":
199
- return self
205
+ __sub__ = __add__
200
206
 
201
207
  def __pos__(self) -> "GF2nPoint":
202
208
  return self
203
209
 
204
- def __sub__(self, other: "GF2nPoint") -> "GF2nPoint":
205
- if not isinstance(other, self.__class__):
206
- return NotImplemented
207
- if self.field != other.field:
208
- raise NotImplementedError("Cannot add elements from different fields.")
209
- return self.field(self.x ^ other.x)
210
+ __neg__ = __pos__
210
211
 
211
212
  def __mul__(self, other: "GF2nPoint") -> "GF2nPoint":
212
213
  if isinstance(other, int):
213
214
  if other % 2:
214
215
  return self
215
- return self.field(0)
216
+ return self.__class__(0, self.field)
216
217
  if not isinstance(other, self.__class__):
217
218
  return NotImplemented
218
219
  if self.field != other.field:
@@ -238,13 +239,13 @@ class GF2nPoint:
238
239
  y <<= 1
239
240
  if y & bitmap1:
240
241
  y ^= self.field.modulus
241
- return self.field(z)
242
+ return self.__class__(z, self.field)
242
243
 
243
244
  def __rmul__(self, scalar: int) -> "GF2nPoint":
244
245
  if isinstance(scalar, int):
245
246
  if scalar % 2:
246
247
  return self
247
- return self.field(0)
248
+ return self.__class__(0, self.field)
248
249
  return NotImplemented
249
250
 
250
251
  def __truediv__(self, other: "GF2nPoint") -> "GF2nPoint":
@@ -261,14 +262,16 @@ class GF2nPoint:
261
262
  return NotImplemented
262
263
  if not (self.x) and j < 0:
263
264
  raise ValueError("Division by zero.")
265
+ j %= self.field.order - 1
264
266
  if self.field.order == 2:
267
+ if j == 0:
268
+ return self.field.one()
265
269
  return self
266
270
  if self.field.bitreversed:
267
- res = self.field(self.field.order >> 1)
271
+ res = self.__class__(self.field.order >> 1, self.field)
268
272
  else:
269
- res = self.field(1)
270
- x = self.field(self.x)
271
- j %= self.field.order - 1
273
+ res = self.__class__(1, self.field)
274
+ x = self.__class__(self.x, self.field)
272
275
  while j > 0:
273
276
  # If j is odd, multiply with x
274
277
  if j & 1:
@@ -282,13 +285,13 @@ class GF2nPoint:
282
285
  "Cyclic rotation to the left."
283
286
  x = self.x << j
284
287
  x = (x % self.field.order) + (x // self.field.order)
285
- return self.field(x)
288
+ return self.__class__(self.x, self.field)
286
289
 
287
290
  def __rshift__(self, j: int) -> "GF2nPoint":
288
291
  "Cyclic rotation to the right."
289
292
  x = self.x >> j
290
293
  x += (self.field.order >> j) * (self.x % 2**j)
291
- return self.field(x)
294
+ return self.__class__(self.x, self.field)
292
295
 
293
296
  def sqrt(self) -> "GF2nPoint":
294
297
  "Compute the square root."
@@ -314,6 +317,17 @@ class GF2nPoint:
314
317
  "Test if the point is a generator of the group GF(2^n)^*."
315
318
  return self.field.mult_order == self.order()
316
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
+
317
331
  def sbox(self, inv: bool = False) -> "GF2nPoint":
318
332
  "Apply the AES sbox."
319
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.1"
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."
@@ -102,7 +70,7 @@ class Goppa():
102
70
  self.G = gen2pchk(self.H)
103
71
 
104
72
  def __repr__(self):
105
- return f"Binary irreducible Goppa code over GF(2^{self.gf.power}) with polynomial g(x) = {self.g}."
73
+ return f"Binary irreducible Goppa [{self.G.cols}, {self.G.rows}] code over GF(2^{self.gf.degree}) with polynomial g(x) = {self.g}."
106
74
 
107
75
  def encode(self, x: list[int]):
108
76
  "Encode a given list of bits."
@@ -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]
@@ -246,12 +219,12 @@ class ReedSolomonCode(CyclicCode):
246
219
  >>> rsc = ReedSolomonCode(k, gf)
247
220
 
248
221
  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):
249
- >>> cc.encode([1, 2, 3])
250
- [0, 1, 1, 0]
222
+ >>> rsc.encode([1, 2, 3])
223
+ [6, 4, 5, 1, 8, 4, 2, 9, 2, 8, 9, 6]
251
224
 
252
- Decoding is only implemnted for code words:
253
- >>> cc.decode([0, 1, 1, 0], ring = Zmod(2))
254
- [0, 1, 0]
225
+ To decode use:
226
+ >>> rsc.decode([1, 4, 4, 0, 8, 4, 2, 9, 2, 0, 9, 6])
227
+ [1, 2, 3]
255
228
 
256
229
  """
257
230
  def __init__(self, k:int, gf, alpha = None): # pylint: disable=W0231
@@ -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"):
@@ -279,9 +254,9 @@ class ReedSolomonCode(CyclicCode):
279
254
  assert self.g * self.h == self.modulus
280
255
 
281
256
  def __repr__(self):
282
- return f"Reed-Solomon-Code [{self.n}, {self.k}, {self.d}] over GF({self.n+1})."
257
+ return f"Reed-Solomon [{self.n}, {self.k}, {self.d}] code over GF({self.n+1})."
283
258
 
284
- def encode(self, x: list|Poly): # pylint: disable=W0221
259
+ def encode(self, x: list|Poly) -> list|Poly: # pylint: disable=W0221
285
260
  "Encode a given list or polynomial."
286
261
  as_list = False
287
262
  zero = 0 * self.g[0]
@@ -295,7 +270,7 @@ class ReedSolomonCode(CyclicCode):
295
270
  return y + [zero] * (self.n - len(y))
296
271
  return Poly(y)
297
272
 
298
- def decode(self, y: list|Poly): # pylint: disable=W0221
273
+ def decode(self, y: list|Poly) -> list|Poly: # pylint: disable=W0221
299
274
  "Decode a given list or polynomial."
300
275
  as_list = False
301
276
  zero = 0 * self.g[0]
@@ -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
@@ -130,6 +135,10 @@ class Poly:
130
135
  "Return the degree."
131
136
  return len(self.coeff) - 1
132
137
 
138
+ def weight(self) -> int:
139
+ "Return the number of nonzero coefficients."
140
+ return sum(map(bool,self.coeff))
141
+
133
142
  def map(self, func):
134
143
  "Apply a given function to all coefficients in place."
135
144
  self.coeff = list(map(func, self.coeff))
@@ -294,7 +303,7 @@ class Poly:
294
303
  return self.__class__([zero]), self
295
304
  div = [zero] * (sd - od + 1)
296
305
  lco = other.coeff[-1]
297
- if bool(lco - one):
306
+ if lco != one:
298
307
  tmp = one / lco
299
308
  oth = [c * tmp for c in other.coeff]
300
309
  rem = [c * tmp for c in self.coeff]
@@ -306,7 +315,7 @@ class Poly:
306
315
  div[sd - od - i] = tmp
307
316
  for j in range(od + 1):
308
317
  rem[sd - i - j] -= tmp * oth[od - j]
309
- if bool(lco - one):
318
+ if lco != one:
310
319
  rem = [c * lco for c in rem]
311
320
  return self.__class__(div, modulus=self.modulus), self.__class__(rem, modulus=self.modulus)
312
321
 
@@ -323,7 +332,7 @@ class Poly:
323
332
  if sd < od:
324
333
  return self
325
334
  lco = other.coeff[-1]
326
- if bool(lco - one):
335
+ if lco != one:
327
336
  tmp = one / lco
328
337
  oth = [c * tmp for c in other.coeff]
329
338
  else:
@@ -359,12 +368,15 @@ class Poly:
359
368
  if r0.degree() != 0:
360
369
  raise ValueError(f"{self} is not invertible mod {other}.")
361
370
  y0.modulus = self.modulus
371
+ if r0.coeff[0] == one:
372
+ return y0
362
373
  return y0 / r0.coeff[0]
363
374
 
364
375
  def gcd(self, other: "Poly") -> "Poly":
365
376
  "Greates common divisor with a given polynomial."
366
377
  if not isinstance(other, self.__class__):
367
378
  raise NotImplementedError(f"Cannot compute gcd: {other} must be a polynomial.")
379
+ _, one, _ = self._guess_ring()
368
380
  if not self:
369
381
  if not other:
370
382
  return Poly([self.coeff[0]])
@@ -375,8 +387,14 @@ class Poly:
375
387
  while r1:
376
388
  r0, r1 = r1, r0.divmod(r1)[1]
377
389
  r0.modulus = None
390
+ if r0.coeff[-1] == one:
391
+ return r0
378
392
  return r0 / r0.coeff[-1]
379
393
 
394
+ def lcm(self, other: "Poly") -> "Poly":
395
+ "Least common multiple with a given polynomial."
396
+ return self * other // self.gcd(other)
397
+
380
398
  def egcd(self, other: "Poly") -> ("Poly", "Poly", "Poly"):
381
399
  """Perform the extended Euclidean agorithm for polynomials. Returns gcd, x, y such that other * x + self * y = gcd."""
382
400
  zero, one, _ = self._guess_ring()
@@ -385,6 +403,8 @@ class Poly:
385
403
  if not self:
386
404
  if not other:
387
405
  return Poly([zero]), Poly([zero]), Poly([zero])
406
+ if other.coeff[-1] == one:
407
+ return other, Poly([zero]), one
388
408
  return other / other.coeff[-1], Poly([zero]), one / other.coeff[-1]
389
409
  r0, r1 = other, self
390
410
  x0, x1 = self.__class__([one]), self.__class__([zero])
@@ -395,6 +415,8 @@ class Poly:
395
415
  x0, x1 = x1, x0 - x1 * q
396
416
  y0, y1 = y1, y0 - y1 * q
397
417
  lcr0 = r0.coeff[-1]
418
+ if lcr0 == one:
419
+ return r0, x0, y0
398
420
  return r0 / lcr0, x0 / lcr0, y0 / lcr0
399
421
 
400
422
  def derivative(self) -> "Poly":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.5.2
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.2"
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
@@ -8,9 +8,17 @@ seed(0)
8
8
  num_tests = 10
9
9
 
10
10
  def test_Poly():
11
+ assert Poly([0]).weight() == 0
12
+ assert Poly([1]).weight() == 1
13
+ assert Poly([1]).degree() == 0
11
14
  p = Poly([2, 0, 2])
12
15
  q = Poly([1, 2, 3, 4])
16
+ assert p.degree() == 2
17
+ assert p.weight() == 2
18
+ assert q.degree() == 3
19
+ assert q.weight() == 4
13
20
  d, m = q.divmod(p)
21
+ assert q * p == p.gcd(q) * p.lcm(q)
14
22
  assert d * p + m == q
15
23
  for _ in range(num_tests):
16
24
  p = Poly([randint(0,100) for _ in range(10)])
@@ -26,6 +34,7 @@ def test_Poly():
26
34
  q = Poly([1, 2, 3, 4], ring = gf)
27
35
  d, m = q.divmod(p)
28
36
  assert d * p + m == q
37
+ assert q * p == p.gcd(q) * p.lcm(q)
29
38
  for _ in range(num_tests):
30
39
  p = Poly(gf.random(10))
31
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