kryptools 1.3.5__tar.gz → 1.5__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 (59) hide show
  1. {kryptools-1.3.5 → kryptools-1.5}/PKG-INFO +4 -2
  2. {kryptools-1.3.5 → kryptools-1.5}/README.md +3 -1
  3. {kryptools-1.3.5 → kryptools-1.5}/kryptools/GF2.py +31 -19
  4. {kryptools-1.3.5 → kryptools-1.5}/kryptools/Zmod.py +24 -5
  5. {kryptools-1.3.5 → kryptools-1.5}/kryptools/__init__.py +3 -3
  6. kryptools-1.5/kryptools/code.py +328 -0
  7. {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp_qs.py +1 -1
  8. {kryptools-1.3.5 → kryptools-1.5}/kryptools/poly.py +208 -64
  9. {kryptools-1.3.5 → kryptools-1.5}/kryptools.egg-info/PKG-INFO +4 -2
  10. {kryptools-1.3.5 → kryptools-1.5}/pyproject.toml +1 -1
  11. {kryptools-1.3.5 → kryptools-1.5}/tests/test_GF2.py +1 -1
  12. {kryptools-1.3.5 → kryptools-1.5}/tests/test_code.py +22 -1
  13. kryptools-1.5/tests/test_poly.py +63 -0
  14. kryptools-1.3.5/kryptools/code.py +0 -140
  15. kryptools-1.3.5/tests/test_poly.py +0 -36
  16. {kryptools-1.3.5 → kryptools-1.5}/LICENSE +0 -0
  17. {kryptools-1.3.5 → kryptools-1.5}/kryptools/aes.py +0 -0
  18. {kryptools-1.3.5 → kryptools-1.5}/kryptools/blockcipher.py +0 -0
  19. {kryptools-1.3.5 → kryptools-1.5}/kryptools/conway_polynomials.py +0 -0
  20. {kryptools-1.3.5 → kryptools-1.5}/kryptools/des.py +0 -0
  21. {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp.py +0 -0
  22. {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp_bsgs.py +0 -0
  23. {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp_ic.py +0 -0
  24. {kryptools-1.3.5 → kryptools-1.5}/kryptools/dlp_rho.py +0 -0
  25. {kryptools-1.3.5 → kryptools-1.5}/kryptools/ec.py +0 -0
  26. {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor.py +0 -0
  27. {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_dix.py +0 -0
  28. {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_ecm.py +0 -0
  29. {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_fmt.py +0 -0
  30. {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_pm1.py +0 -0
  31. {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_qs.py +0 -0
  32. {kryptools-1.3.5 → kryptools-1.5}/kryptools/factor_rho.py +0 -0
  33. {kryptools-1.3.5 → kryptools-1.5}/kryptools/intfuncs.py +0 -0
  34. {kryptools-1.3.5 → kryptools-1.5}/kryptools/keccak.py +0 -0
  35. {kryptools-1.3.5 → kryptools-1.5}/kryptools/la.py +0 -0
  36. {kryptools-1.3.5 → kryptools-1.5}/kryptools/lat.py +0 -0
  37. {kryptools-1.3.5 → kryptools-1.5}/kryptools/nt.py +0 -0
  38. {kryptools-1.3.5 → kryptools-1.5}/kryptools/primes.py +0 -0
  39. {kryptools-1.3.5 → kryptools-1.5}/kryptools/sha1.py +0 -0
  40. {kryptools-1.3.5 → kryptools-1.5}/kryptools.egg-info/SOURCES.txt +0 -0
  41. {kryptools-1.3.5 → kryptools-1.5}/kryptools.egg-info/dependency_links.txt +0 -0
  42. {kryptools-1.3.5 → kryptools-1.5}/kryptools.egg-info/top_level.txt +0 -0
  43. {kryptools-1.3.5 → kryptools-1.5}/setup.cfg +0 -0
  44. {kryptools-1.3.5 → kryptools-1.5}/tests/test_Zmod.py +0 -0
  45. {kryptools-1.3.5 → kryptools-1.5}/tests/test_aes.py +0 -0
  46. {kryptools-1.3.5 → kryptools-1.5}/tests/test_des.py +0 -0
  47. {kryptools-1.3.5 → kryptools-1.5}/tests/test_dlog.py +0 -0
  48. {kryptools-1.3.5 → kryptools-1.5}/tests/test_ec.py +0 -0
  49. {kryptools-1.3.5 → kryptools-1.5}/tests/test_factor.py +0 -0
  50. {kryptools-1.3.5 → kryptools-1.5}/tests/test_factor_fmt.py +0 -0
  51. {kryptools-1.3.5 → kryptools-1.5}/tests/test_factor_pm1.py +0 -0
  52. {kryptools-1.3.5 → kryptools-1.5}/tests/test_factor_qs.py +0 -0
  53. {kryptools-1.3.5 → kryptools-1.5}/tests/test_intfuncs.py +0 -0
  54. {kryptools-1.3.5 → kryptools-1.5}/tests/test_keccak.py +0 -0
  55. {kryptools-1.3.5 → kryptools-1.5}/tests/test_la.py +0 -0
  56. {kryptools-1.3.5 → kryptools-1.5}/tests/test_lat.py +0 -0
  57. {kryptools-1.3.5 → kryptools-1.5}/tests/test_nt.py +0 -0
  58. {kryptools-1.3.5 → kryptools-1.5}/tests/test_primes.py +0 -0
  59. {kryptools-1.3.5 → kryptools-1.5}/tests/test_sha1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.3.5
3
+ Version: 1.5
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
@@ -42,10 +42,12 @@ The tools contained are:
42
42
  * linear codes: Hamming distance, left standard form, parity check matrix
43
43
 
44
44
  * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
45
- * Poly: a class for polynomials (division, modulo)
45
+ * Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
46
46
  * Zmod: a class for the ring of integers modulo an integer
47
47
  * GF2: a class for Galois fields GF(2^n)
48
48
  * Goppa: a class for encoding and decoding Goppa codes
49
+ * CyclicCode: a class for encoding and decoding cyclic codes
50
+ * ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
49
51
 
50
52
  * BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
51
53
  * AESCipher: individual AES operations
@@ -24,10 +24,12 @@ The tools contained are:
24
24
  * linear codes: Hamming distance, left standard form, parity check matrix
25
25
 
26
26
  * Matrix: a class for Matrices (inverse, det, reduced echelon form, etc.)
27
- * Poly: a class for polynomials (division, modulo)
27
+ * Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
28
28
  * Zmod: a class for the ring of integers modulo an integer
29
29
  * GF2: a class for Galois fields GF(2^n)
30
30
  * Goppa: a class for encoding and decoding Goppa codes
31
+ * CyclicCode: a class for encoding and decoding cyclic codes
32
+ * ReedSolomonCode: a class for encoding and decoding Reed-Solomon codes
31
33
 
32
34
  * BlockCipher: a class implementing the usual modes of operation (ECB, CBC, GCM, etc.)
33
35
  * AESCipher: individual AES operations
@@ -3,6 +3,7 @@ Galois field GF(2^n).
3
3
  """
4
4
 
5
5
  from math import gcd
6
+ from random import randint
6
7
  from .poly import Poly
7
8
  from .Zmod import Zmod
8
9
  from .factor import factorint
@@ -41,13 +42,14 @@ class GF2:
41
42
  if n in conway_polynomials2:
42
43
  modulus = conway_polynomials2[n]
43
44
  else:
44
- raise ValueError("Unknown power. Please specify a modulus.")
45
+ raise ValueError("Unknown degree. Please specify a modulus.")
45
46
  self.modulus = modulus # integer whose biniary digits are the coefficients of the irreducible modulus polynomial
46
47
  if modulus & 2**n == 2**n:
47
48
  self.bitreversed = False
48
49
  else:
49
50
  self.bitreversed = True
50
- self.power = n # n
51
+ self.degree = n # n
52
+ self.characteristic = 2
51
53
  self.order = 2**n # 2**n
52
54
  self.print_hex: bool = True
53
55
  self.byteorder: str = "big" # big|little
@@ -55,10 +57,10 @@ class GF2:
55
57
  self.factors = {} # factoring of the group order
56
58
 
57
59
  def __repr__(self):
58
- return f"GF(2^{self.power})"
60
+ return f"GF(2^{self.degree})"
59
61
 
60
- def __call__(self, x: int | list | tuple):
61
- if isinstance(x, list|tuple):
62
+ def __call__(self, x: int | list | tuple | range | map):
63
+ if isinstance(x, list|tuple|range|map):
62
64
  return [GF2nPoint(xx, self) for xx in x]
63
65
  return GF2nPoint(x, self)
64
66
 
@@ -74,7 +76,7 @@ class GF2:
74
76
  return self.order == other.order and self.modulus == other.modulus
75
77
  return False
76
78
 
77
- def __contains__(self, other: "ZmodPoint") -> bool:
79
+ def __contains__(self, other: "GF2nPoint") -> bool:
78
80
  return isinstance(other, GF2nPoint) and self == other.field
79
81
 
80
82
  def _factors(self) -> dict:
@@ -83,12 +85,22 @@ class GF2:
83
85
  self.factors = factorint(self.mult_order)
84
86
  return self.factors
85
87
 
88
+ def zero(self) -> bool:
89
+ "Return zero."
90
+ return self(0)
91
+
86
92
  def one(self) -> bool:
87
93
  "Return one."
88
94
  if self.bitreversed:
89
95
  return self(self.order - 1)
90
96
  return self(1)
91
97
 
98
+ def random(self, num: int = 0) -> "GF2nPoint":
99
+ "Return a single random point or a list of random points."
100
+ if num:
101
+ return([self(randint(0,self.order-1)) for _ in range(num)])
102
+ return self(randint(0,self.order-1))
103
+
92
104
  def is_cyclic(self) -> bool:
93
105
  "GF(2^n)^* is cyclic."
94
106
  return True
@@ -99,7 +111,7 @@ class GF2:
99
111
 
100
112
  def generator(self) -> int | None:
101
113
  "Return a generator of the group GF(2^n)^*."
102
- if self.power == 1:
114
+ if self.degree == 1:
103
115
  return self(1)
104
116
  for a in range(2, self.order):
105
117
  a = self(a)
@@ -131,7 +143,7 @@ class GF2nPoint:
131
143
 
132
144
  def __repr__(self):
133
145
  if self.field.print_hex:
134
- return format(self.x, "0"+str(self.field.power // 4)+"x")
146
+ return format(self.x, "0"+str(self.field.degree // 4)+"x")
135
147
  return self.x
136
148
 
137
149
  def __eq__(self, other):
@@ -146,16 +158,16 @@ class GF2nPoint:
146
158
  return self.x
147
159
 
148
160
  def __bytes__(self):
149
- return self.x.to_bytes(self.field.power // 8, byteorder=self.field.byteorder)
161
+ return self.x.to_bytes(self.field.degree // 8, byteorder=self.field.byteorder)
150
162
 
151
163
  def bits(self) -> list:
152
164
  "Convert to a list of bits."
153
165
  if self.field.bitreversed:
154
166
  coeff = [int(d) for d in str(
155
- format(self.x, "0" + str(self.field.power) + "b"))]
167
+ format(self.x, "0" + str(self.field.degree) + "b"))]
156
168
  else:
157
169
  coeff = reversed([int(d) for d in str(
158
- format(self.x, "0" + str(self.field.power) + "b"))])
170
+ format(self.x, "0" + str(self.field.degree) + "b"))])
159
171
  return list(coeff)
160
172
 
161
173
  def poly(self) -> "Poly":
@@ -163,14 +175,14 @@ class GF2nPoint:
163
175
  Z_2 = Zmod(2)
164
176
  if self.field.bitreversed:
165
177
  coeff = [Z_2(int(d)) for d in str(
166
- format(self.x, "0" + str(self.field.power) + "b"))]
178
+ format(self.x, "0" + str(self.field.degree) + "b"))]
167
179
  coeff_modulus = [Z_2(int(d)) for d in str(
168
- format((self.field.modulus << 1) + 1, "0" + str(self.field.power) + "b"))]
180
+ format((self.field.modulus << 1) + 1, "0" + str(self.field.degree) + "b"))]
169
181
  else:
170
182
  coeff = reversed([Z_2(int(d)) for d in str(
171
- format(self.x, "0" + str(self.field.power) + "b"))])
183
+ format(self.x, "0" + str(self.field.degree) + "b"))])
172
184
  coeff_modulus = reversed([Z_2(int(d)) for d in str(
173
- format(self.field.modulus, "0" + str(self.field.power) + "b"))])
185
+ format(self.field.modulus, "0" + str(self.field.degree) + "b"))])
174
186
  return Poly(list(coeff), modulus=list(coeff_modulus))
175
187
 
176
188
  def __hash__(self):
@@ -208,7 +220,7 @@ class GF2nPoint:
208
220
  z = 0
209
221
  y = other.x
210
222
  if self.field.bitreversed:
211
- for d in bin(self.x)[2:].zfill(self.field.power):
223
+ for d in bin(self.x)[2:].zfill(self.field.degree):
212
224
  if int(d):
213
225
  z ^= y
214
226
  if y % 2:
@@ -219,7 +231,7 @@ class GF2nPoint:
219
231
  else:
220
232
  bitmap1 = self.field.order
221
233
  bitmap2 = 1
222
- for _ in range(self.field.power):
234
+ for _ in range(self.field.degree):
223
235
  if self.x & bitmap2:
224
236
  z ^= y
225
237
  bitmap2 <<= 1
@@ -300,11 +312,11 @@ class GF2nPoint:
300
312
 
301
313
  def sbox(self, inv: bool = False) -> "GF2nPoint":
302
314
  "Apply the AES sbox."
303
- if self.field.power == 8 and self.field.modulus == 0b100011011: # AES
315
+ if self.field.degree == 8 and self.field.modulus == 0b100011011: # AES
304
316
  if inv:
305
317
  return aes_sbox_inv[self.x]
306
318
  return aes_sbox[self.x]
307
- elif self.field.power == 4 and self.field.modulus == 0b10011: # MiniAES
319
+ elif self.field.degree == 4 and self.field.modulus == 0b10011: # MiniAES
308
320
  if inv:
309
321
  return miniaes_sbox_inv[self.x]
310
322
  return miniaes_sbox[self.x]
@@ -3,6 +3,7 @@ Ring of intergers modulo `n`.
3
3
  """
4
4
 
5
5
  from math import gcd
6
+ from random import randint
6
7
  from .factor import factorint
7
8
  from .primes import is_prime
8
9
  from .nt import sqrt_mod, crt
@@ -31,16 +32,17 @@ class Zmod:
31
32
  if not isinstance(n, int) or n < 1:
32
33
  raise ValueError(f"{n} is not a positive integer.")
33
34
  self.n = n
35
+ self.isfield = None
34
36
  self.n_factors = {} # factoring of n
35
37
  self.short = short
36
- self.group_order = 0
38
+ self.group_order = 0 # order of the multiplicative group
37
39
  self.factors = {} # factoring of the multiplicative group order
38
40
 
39
41
  def __repr__(self):
40
42
  return f"Z_{self.n}"
41
43
 
42
- def __call__(self, x: int | list | tuple):
43
- if isinstance(x, list|tuple):
44
+ def __call__(self, x: int | list | tuple | range | map):
45
+ if isinstance(x, list|tuple|range|map):
44
46
  return [ZmodPoint(xx, self) for xx in x]
45
47
  return ZmodPoint(x, self)
46
48
 
@@ -56,6 +58,12 @@ class Zmod:
56
58
  def __contains__(self, other: "ZmodPoint") -> bool:
57
59
  return isinstance(other, ZmodPoint) and self.n == other.ring.n
58
60
 
61
+ def random(self, num: int = 0) -> "ZmodPoint":
62
+ "Return a single random point or a list of random points."
63
+ if num:
64
+ return([self(randint(0,self.n-1)) for _ in range(num)])
65
+ return self(randint(0,self.n-1))
66
+
59
67
  def order(self) -> int:
60
68
  "Compute the order of the group Z_n^*."
61
69
  if self.group_order:
@@ -63,7 +71,7 @@ class Zmod:
63
71
  # We compute euler_phi(n) and its factorization in one pass
64
72
  if not self.n_factors:
65
73
  self.n_factors = factorint(self.n)
66
- for p, k in self.n_factors.items(): # first factorize n
74
+ for p, k in self.n_factors.items(): # factorization of n
67
75
  # factor p-1 and add the factors
68
76
  for pm, km in factorint(p - 1).items():
69
77
  if pm in self.factors:
@@ -96,7 +104,18 @@ class Zmod:
96
104
 
97
105
  def is_field(self) -> bool:
98
106
  "Test if Z_n is a field."
99
- return is_prime(self.n)
107
+ if self.isfield is None:
108
+ if self.n_factors:
109
+ if len(self.n_factors) == 1:
110
+ self.isfield = True
111
+ else:
112
+ self.isfield = False
113
+ elif is_prime(self.n):
114
+ self.isfield = True
115
+ self.n_factors = { self.n: 1}
116
+ else:
117
+ self.isfield = False
118
+ return self.isfield
100
119
 
101
120
  def generator(self) -> int | None:
102
121
  "Return a generator of 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.3.5"
8
+ __version__ = "1.5"
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
@@ -16,10 +16,10 @@ from .dlp import dlog
16
16
  from .ec import EC_Weierstrass
17
17
  from .la import Matrix, zeros, eye
18
18
  from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round_cvp, babai_round_bnd, babai_plane_cvp, babai_plane_bnd, lagrange_lr, lll, random_unimodular_matrix
19
- from .poly import Poly
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
22
+ from .code import left_standard_form, gen2pchk, hamming_dist, Goppa, CyclicCode, ReedSolomonCode
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
@@ -0,0 +1,328 @@
1
+ """
2
+ Linear codes
3
+ """
4
+
5
+ from math import prod
6
+ from .Zmod import Zmod
7
+ from .GF2 import GF2
8
+ from .poly import Poly, lagrange_interpolation
9
+ from .la import Matrix, eye, zeros
10
+
11
+
12
+ def poly2bits(x: Poly) -> list:
13
+ "Convert a polynomial in GF(2^n)[x] to a list of binary digits."
14
+ out = []
15
+ for c in x.coeff:
16
+ out += c.bits()
17
+ return out
18
+
19
+ def left_standard_form(M: Matrix) -> (Matrix, Matrix):
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
39
+
40
+ def gen2pchk(G: Matrix) -> Matrix:
41
+ "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()
58
+
59
+ def hamming_dist(a: iter, b: iter):
60
+ "Hamming distance of two iterables."
61
+ if len(a) != len(b):
62
+ raise ValueError("Both iteralbles must have equal length.")
63
+ return sum(aa != bb for aa, bb in zip(a, b))
64
+
65
+ class Goppa():
66
+ """
67
+ Binary irreducible Goppa code.
68
+
69
+ Example:
70
+
71
+ >>> gf = GF2(4) # base field GF(2^4)
72
+ >>> g = Poly([2, 0, 0, 1], ring = gf) # irreducible Polynomial for the code
73
+ >>> alpha = list(gf) # list of points from the base field (must not contain zeros of g)
74
+ >>> goppa = Goppa(gf, g, alpha)
75
+
76
+ To encode a list of bits (the length must be equal to the dimension k of the code)
77
+ >>> goppa.encode([1, 0, 1, 1])
78
+ [0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1]
79
+
80
+ Decoding still works if there are at most deg(g) errors:
81
+ >>> goppa.decode([0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0])
82
+ [1, 0, 1, 1]
83
+
84
+ """
85
+
86
+ def __init__(self, gf: GF2, g: Poly, alpha: list["GF2Point"]):
87
+ if not all(map(lambda x: x in gf, alpha)):
88
+ raise ValueError(f"{x}: alpha must be a list of elements from the Galois field!")
89
+ if not g.rabin_test():
90
+ raise ValueError("The polynomial g must be irreducible!")
91
+ self.gf = gf # underlying Galois field
92
+ self.g = g # polynomial
93
+ self.alpha = alpha # list of elements from gf
94
+ # Compute the control matrix
95
+ self.Z_2 = Zmod(2)
96
+
97
+ tmp =[]
98
+ for x in alpha:
99
+ tmp.append(Poly([-x, 1], ring = gf, modulus = g).inv().bits())
100
+ self.H = Matrix(tmp, ring=self.Z_2).transpose()
101
+ # Compute the generator matrix
102
+ self.G = gen2pchk(self.H)
103
+
104
+ def __repr__(self):
105
+ return f"Binary irreducible Goppa code over GF(2^{self.gf.power}) with polynomial g(x) = {self.g}."
106
+
107
+ def encode(self, x: list[int]):
108
+ "Encode a given list of bits."
109
+ x = Matrix(x, ring = self.Z_2)
110
+ y = self.G.transpose() * x
111
+ y.map(int)
112
+ return list(y)
113
+
114
+ def decode(self, y: list[int]):
115
+ "Decode a given list of bits."
116
+ y = Matrix(y, ring = self.Z_2)
117
+ zero = self.gf(0)
118
+ one = zero**0
119
+ if self.H * y: # no code word
120
+ s = zero # syndrom polynomial
121
+ for i, x in enumerate(self.alpha):
122
+ s += int(y[i]) * Poly([-x, one], modulus = self.g).inv()
123
+ v = (s.inv() - Poly([zero, one], modulus = self.g))**(self.gf.order ** self.g.degree() // 2)
124
+ # determine error locator polynomial sigma(x) = a(x^2) + b(x^2) * x
125
+ # find a and b by running EEA until b has the desired degree
126
+ v.modulus = None
127
+ r0, r1 = self.g, v
128
+ y0, y1 = Poly([zero]), Poly([one])
129
+ while 2 * r1.degree() > self.g.degree():
130
+ q, r = r0.divmod(r1)
131
+ r0, r1 = r1, r
132
+ y0, y1 = y1, y0 - q * y1
133
+ a, b = r1, y1
134
+ sigma = a**2 + b**2 * Poly([zero, one])
135
+ # correct the error
136
+ for x in self.alpha:
137
+ if not sigma(x):
138
+ y[int(x)] += 1
139
+ if self.H * y:
140
+ raise ValueError("Decoding failed!")
141
+ x = self.G.transpose().solve(y)
142
+ x.map(int)
143
+ return list(x)
144
+
145
+ class CyclicCode():
146
+ """
147
+ Cyclic code.
148
+
149
+ Example:
150
+
151
+ >>> n = 4 # length of the code
152
+ >>> g = Poly([1, 1], ring = Zmod(2)) # generating Polynomial for the code
153
+ >>> cc = CyclicCode(n, g)
154
+
155
+ To encode a list of letters (the length must be equal to the dimension k of the code):
156
+ >>> cc.encode([0,1,0], ring = Zmod(2))
157
+ [0, 1, 1, 0]
158
+
159
+ Decoding is only implemnted for code words:
160
+ >>> cc.decode([0, 1, 1, 0], ring = Zmod(2))
161
+ [0, 1, 0]
162
+
163
+ """
164
+
165
+ def __init__(self, n:int, g: Poly):
166
+ if n < 2:
167
+ raise ValueError(f"Code lenght {n} must be at least 2!")
168
+ zero = 0 * g[0]
169
+ try:
170
+ order = zero.ring.n # Zmod
171
+ except:
172
+ try:
173
+ order = zero.field.order # GF2
174
+ except:
175
+ try:
176
+ order = type(zero).order # galois
177
+ except:
178
+ raise ValueError("Unknown base filed!")
179
+ self.order = order # order of the base field
180
+ one = zero**0
181
+ modulus = Poly([-one] + [zero] * (n-1) + [one])
182
+ self.modulus = modulus
183
+ if modulus % g:
184
+ raise ValueError(f"The polynomial g must be a factor of x^{n} - 1!")
185
+ self.n = n # lenght
186
+ self.k = n - g.degree() # dimension
187
+ self.g = g # generating polynomial
188
+ self.h = Poly(modulus) // g
189
+
190
+ def __repr__(self):
191
+ return f"Cyclic [{self.n}, {self.k}] code over GF({self.order}) with generator polynomial g(x) = {self.g}."
192
+
193
+ def encode(self, x: list|Poly, gf = None, systematic_form = False):
194
+ "Encode a given list or polynomial."
195
+ as_list = False
196
+ zero = 0 * self.g[0]
197
+ if isinstance(x, list | tuple):
198
+ as_list = True
199
+ x = Poly(x, ring = gf)
200
+ if x.degree() >= self.k:
201
+ raise ValueError(f"Degree can be at most {self.k-1}.")
202
+ if systematic_form:
203
+ x.coeff = [zero] * self.g.degree() + x.coeff
204
+ y = x - (x % self.g)
205
+ else:
206
+ y = self.g * x
207
+ if as_list:
208
+ return y.coeff + [zero] * (self.n - len(y.coeff))
209
+ return y
210
+
211
+ def decode(self, y: list|Poly, gf = None, systematic_form = False):
212
+ "Decode a given list or polynomial."
213
+ as_list = False
214
+ zero = 0 * self.g[0]
215
+ if isinstance(y, list | tuple):
216
+ as_list = True
217
+ y = Poly(y, ring = gf)
218
+ if y.degree() >= self.n:
219
+ raise ValueError(f"Degree can be at most {self.n-1}.")
220
+ x, r = y.divmod(self.g)
221
+ if r:
222
+ raise NotImplementedError(f"Word is no code word! Cannot decode.")
223
+ if systematic_form:
224
+ x.coeff = y.coeff[self.g.degree():]
225
+ if as_list:
226
+ return x.coeff + [zero] * (self.k - len(x.coeff))
227
+ return x
228
+
229
+ def generator_matrix(self):
230
+ "Return the generator matrix of the code."
231
+ zero = 0 * self.g[0]
232
+ return Matrix([[zero] * j + self.g.coeff + [zero] * (self.k - j - 1 ) for j in range(self.k)])
233
+
234
+ def check_matrix(self):
235
+ "Return the parity check matrix of the code."
236
+ zero = 0 * self.g[0]
237
+ tmp = list(reversed(self.h.coeff))
238
+ return Matrix([[zero] * j + tmp + [zero] * (self.n - self.k - j - 1 ) for j in range(self.n-self.k)])
239
+
240
+ class ReedSolomonCode(CyclicCode):
241
+ """
242
+ Reed-Solomon code.
243
+
244
+ Example:
245
+
246
+ >>> gf = Zmod(13) # Galois field
247
+ >>> k = 3 # dimension of the code
248
+ >>> rsc = ReedSolomonCode(k, gf)
249
+
250
+ 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):
251
+ >>> cc.encode([1, 2, 3])
252
+ [0, 1, 1, 0]
253
+
254
+ Decoding is only implemnted for code words:
255
+ >>> cc.decode([0, 1, 1, 0], ring = Zmod(2))
256
+ [0, 1, 0]
257
+
258
+ """
259
+ def __init__(self, k:int, gf, alpha = None):
260
+ zero = gf(0)
261
+ one = zero**0
262
+ self. gf = gf
263
+ try:
264
+ n = gf.order()
265
+ except:
266
+ n = gf.order - 1
267
+ self.order = n + 1 # order of the base field
268
+ self.n = n # lenght
269
+ self.k = k # dimension
270
+ self.d = n - k - 1 # minimal distance
271
+ self.modulus = Poly([-one] + [zero] * (n-1) + [one])
272
+ if alpha is None:
273
+ try:
274
+ alpha = gf.generator() # Zmod|GF2
275
+ except:
276
+ alpha = gf.primitive_element # galois
277
+ self.alpha = alpha
278
+ self.g = prod([Poly([-alpha**j, one]) for j in range(1,n-k+1)], start = one) # generating polynomial
279
+ self.h = Poly(self.modulus) // self.g # check polynomial
280
+ assert k == n - self.g.degree()
281
+ assert self.g * self.h == self.modulus
282
+
283
+ def __repr__(self):
284
+ return f"Reed-Solomon-Code [{self.n}, {self.k}, {self.d}] over GF({self.n+1})."
285
+
286
+ def encode(self, x: list|Poly):
287
+ "Encode a given list or polynomial."
288
+ as_list = False
289
+ zero = 0 * self.g[0]
290
+ if isinstance(x, list | tuple):
291
+ as_list = True
292
+ x = Poly(x, ring = self.gf)
293
+ if x.degree() >= self.k:
294
+ raise ValueError(f"Degree can be at most {self.k-1}.")
295
+ y = [ x(self.alpha**j) for j in range(self.n) ]
296
+ if as_list:
297
+ return y + [zero] * (self.n - len(y))
298
+ return Poly(y)
299
+
300
+ def decode(self, y: list|Poly):
301
+ "Decode a given list or polynomial."
302
+ as_list = False
303
+ zero = 0 * self.g[0]
304
+ one = zero**0
305
+ if isinstance(y, list | tuple):
306
+ as_list = True
307
+ y = Poly(y, ring = self.gf)
308
+ if y.degree() >= self.n:
309
+ raise ValueError(f"Degree can be at most {self.n-1}.")
310
+ x = Poly([ -y(self.alpha**(-j)) for j in range(self.n) ])
311
+ if x.degree() >= self.k: # No codewort
312
+ t = (self.n - self.k) // 2
313
+ A = Matrix([[ x[j - l % self.n] for l in range(1, t+1) ] for j in range(self.k+t, self.k+2*t) ])
314
+ b = [ -x[self.k + t + l ] for l in range(t) ]
315
+ lam = Poly([one] + list(A.solve(b)))
316
+ x_coordinates = []
317
+ y_coordinates = []
318
+ for j in range(self.n):
319
+ if len(x_coordinates) == self.k:
320
+ break
321
+ alphaj = self.alpha**j
322
+ if lam(alphaj):
323
+ x_coordinates.append(alphaj)
324
+ y_coordinates.append(y[j])
325
+ x = lagrange_interpolation(x_coordinates, y_coordinates)
326
+ if as_list:
327
+ return x.coeff + [zero] * (self.k - len(x.coeff))
328
+ return x
@@ -97,7 +97,7 @@ def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor:
97
97
  break
98
98
  trys += 1
99
99
  else:
100
- raise Exception(f"Sorry, Quadratic Sieve failed failed to find a new relation after {trys} trys.")
100
+ raise Exception(f"Sorry, Quadratic Sieve failed to find a new relation after {trys} trys.")
101
101
  if verbose > 2:
102
102
  if include_b:
103
103
  print(f"rel found: b*a^{x}=", b * pow(a, x, n) % n, relation)