kryptools 1.3.4__tar.gz → 1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {kryptools-1.3.4 → kryptools-1.4}/PKG-INFO +4 -2
  2. {kryptools-1.3.4 → kryptools-1.4}/README.md +3 -1
  3. {kryptools-1.3.4 → kryptools-1.4}/kryptools/GF2.py +2 -2
  4. {kryptools-1.3.4 → kryptools-1.4}/kryptools/Zmod.py +59 -5
  5. {kryptools-1.3.4 → kryptools-1.4}/kryptools/__init__.py +3 -3
  6. kryptools-1.4/kryptools/code.py +326 -0
  7. {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp_qs.py +1 -1
  8. {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor.py +9 -2
  9. {kryptools-1.3.4 → kryptools-1.4}/kryptools/nt.py +7 -1
  10. {kryptools-1.3.4 → kryptools-1.4}/kryptools/poly.py +20 -0
  11. {kryptools-1.3.4 → kryptools-1.4}/kryptools.egg-info/PKG-INFO +4 -2
  12. {kryptools-1.3.4 → kryptools-1.4}/pyproject.toml +1 -1
  13. {kryptools-1.3.4 → kryptools-1.4}/tests/test_Zmod.py +20 -0
  14. {kryptools-1.3.4 → kryptools-1.4}/tests/test_code.py +22 -1
  15. {kryptools-1.3.4 → kryptools-1.4}/tests/test_poly.py +15 -2
  16. kryptools-1.3.4/kryptools/code.py +0 -140
  17. {kryptools-1.3.4 → kryptools-1.4}/LICENSE +0 -0
  18. {kryptools-1.3.4 → kryptools-1.4}/kryptools/aes.py +0 -0
  19. {kryptools-1.3.4 → kryptools-1.4}/kryptools/blockcipher.py +0 -0
  20. {kryptools-1.3.4 → kryptools-1.4}/kryptools/conway_polynomials.py +0 -0
  21. {kryptools-1.3.4 → kryptools-1.4}/kryptools/des.py +0 -0
  22. {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp.py +0 -0
  23. {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp_bsgs.py +0 -0
  24. {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp_ic.py +0 -0
  25. {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp_rho.py +0 -0
  26. {kryptools-1.3.4 → kryptools-1.4}/kryptools/ec.py +0 -0
  27. {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_dix.py +0 -0
  28. {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_ecm.py +0 -0
  29. {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_fmt.py +0 -0
  30. {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_pm1.py +0 -0
  31. {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_qs.py +0 -0
  32. {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_rho.py +0 -0
  33. {kryptools-1.3.4 → kryptools-1.4}/kryptools/intfuncs.py +0 -0
  34. {kryptools-1.3.4 → kryptools-1.4}/kryptools/keccak.py +0 -0
  35. {kryptools-1.3.4 → kryptools-1.4}/kryptools/la.py +0 -0
  36. {kryptools-1.3.4 → kryptools-1.4}/kryptools/lat.py +0 -0
  37. {kryptools-1.3.4 → kryptools-1.4}/kryptools/primes.py +0 -0
  38. {kryptools-1.3.4 → kryptools-1.4}/kryptools/sha1.py +0 -0
  39. {kryptools-1.3.4 → kryptools-1.4}/kryptools.egg-info/SOURCES.txt +0 -0
  40. {kryptools-1.3.4 → kryptools-1.4}/kryptools.egg-info/dependency_links.txt +0 -0
  41. {kryptools-1.3.4 → kryptools-1.4}/kryptools.egg-info/top_level.txt +0 -0
  42. {kryptools-1.3.4 → kryptools-1.4}/setup.cfg +0 -0
  43. {kryptools-1.3.4 → kryptools-1.4}/tests/test_GF2.py +0 -0
  44. {kryptools-1.3.4 → kryptools-1.4}/tests/test_aes.py +0 -0
  45. {kryptools-1.3.4 → kryptools-1.4}/tests/test_des.py +0 -0
  46. {kryptools-1.3.4 → kryptools-1.4}/tests/test_dlog.py +0 -0
  47. {kryptools-1.3.4 → kryptools-1.4}/tests/test_ec.py +0 -0
  48. {kryptools-1.3.4 → kryptools-1.4}/tests/test_factor.py +0 -0
  49. {kryptools-1.3.4 → kryptools-1.4}/tests/test_factor_fmt.py +0 -0
  50. {kryptools-1.3.4 → kryptools-1.4}/tests/test_factor_pm1.py +0 -0
  51. {kryptools-1.3.4 → kryptools-1.4}/tests/test_factor_qs.py +0 -0
  52. {kryptools-1.3.4 → kryptools-1.4}/tests/test_intfuncs.py +0 -0
  53. {kryptools-1.3.4 → kryptools-1.4}/tests/test_keccak.py +0 -0
  54. {kryptools-1.3.4 → kryptools-1.4}/tests/test_la.py +0 -0
  55. {kryptools-1.3.4 → kryptools-1.4}/tests/test_lat.py +0 -0
  56. {kryptools-1.3.4 → kryptools-1.4}/tests/test_nt.py +0 -0
  57. {kryptools-1.3.4 → kryptools-1.4}/tests/test_primes.py +0 -0
  58. {kryptools-1.3.4 → kryptools-1.4}/tests/test_sha1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.3.4
3
+ Version: 1.4
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, irreducibility test, 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, irreducibility test, 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
@@ -57,8 +57,8 @@ class GF2:
57
57
  def __repr__(self):
58
58
  return f"GF(2^{self.power})"
59
59
 
60
- def __call__(self, x: int | list | tuple):
61
- if isinstance(x, list|tuple):
60
+ def __call__(self, x: int | list | tuple | range | map):
61
+ if isinstance(x, list|tuple|range|map):
62
62
  return [GF2nPoint(xx, self) for xx in x]
63
63
  return GF2nPoint(x, self)
64
64
 
@@ -5,6 +5,7 @@ Ring of intergers modulo `n`.
5
5
  from math import gcd
6
6
  from .factor import factorint
7
7
  from .primes import is_prime
8
+ from .nt import sqrt_mod, crt
8
9
  from .intfuncs import prime_power
9
10
 
10
11
 
@@ -30,15 +31,16 @@ class Zmod:
30
31
  if not isinstance(n, int) or n < 1:
31
32
  raise ValueError(f"{n} is not a positive integer.")
32
33
  self.n = n
34
+ self.n_factors = {} # factoring of n
33
35
  self.short = short
34
36
  self.group_order = 0
35
- self.factors = {} # factoring of the group order
37
+ self.factors = {} # factoring of the multiplicative group order
36
38
 
37
39
  def __repr__(self):
38
- return f"Z_{self.n})"
40
+ return f"Z_{self.n}"
39
41
 
40
- def __call__(self, x: int | list | tuple):
41
- if isinstance(x, list|tuple):
42
+ def __call__(self, x: int | list | tuple | range | map):
43
+ if isinstance(x, list|tuple|range|map):
42
44
  return [ZmodPoint(xx, self) for xx in x]
43
45
  return ZmodPoint(x, self)
44
46
 
@@ -59,7 +61,9 @@ class Zmod:
59
61
  if self.group_order:
60
62
  return self.group_order
61
63
  # We compute euler_phi(n) and its factorization in one pass
62
- for p, k in factorint(self.n).items(): # first factorize n
64
+ if not self.n_factors:
65
+ self.n_factors = factorint(self.n)
66
+ for p, k in self.n_factors.items(): # first factorize n
63
67
  # factor p-1 and add the factors
64
68
  for pm, km in factorint(p - 1).items():
65
69
  if pm in self.factors:
@@ -243,6 +247,56 @@ class ZmodPoint:
243
247
  break
244
248
  return order
245
249
 
250
+ def sqrt(self) -> "ZmodPoint":
251
+ if not self.ring.n_factors:
252
+ self.ring.n_factors = factorint(self.ring.n)
253
+ a = int(self)
254
+ if a in (0, 1):
255
+ return self.ring(a)
256
+ roots = []
257
+ powers = []
258
+ for p, k in self.ring.n_factors.items():
259
+ q = p**(k-1)
260
+ pk = q * p
261
+ ak = a % pk
262
+ powers.append(pk)
263
+ if ak in (0, 1):
264
+ roots.append(ak)
265
+ continue
266
+ # write ak as p^j * invertible
267
+ j = 0
268
+ while ak % p == 0:
269
+ j += 1
270
+ ak //= p
271
+ if j % 2:
272
+ raise ValueError("No quadratic residue!")
273
+ if ak == 1:
274
+ roots.append(p**(j//2))
275
+ continue
276
+ if p == 2:
277
+ if ak % 8 != 1:
278
+ raise ValueError("No quadratic residue!")
279
+ m = 4 # start at 2^2
280
+ x = ak % m # this is the root mod m
281
+ for i in range(3, k + 1):
282
+ # test all 4 poosible roots
283
+ if (x**2 - ak) % (m * 2):
284
+ x = m - x
285
+ if (x**2 - ak) % (m*2):
286
+ x = (x * (m//2-1)) % m
287
+ if (x**2 - ak) % (m * 2):
288
+ x = m - x
289
+ m *= 2
290
+ else:
291
+ # odd prime: Cipolla's formula
292
+ x = sqrt_mod(ak, p)
293
+ if x is None:
294
+ raise ValueError("No quadratic residue!")
295
+ if k > 1:
296
+ x = pow(x, q, pk) * pow(ak,((p-2)*q+1)//2, pk) % pk
297
+ roots.append((p**(j//2) * x) % pk)
298
+ return self.ring(crt(roots, powers))
299
+
246
300
  def is_generator(self):
247
301
  "Test if the point is a generator of the group Z_n^*."
248
302
  return self.ring.order() == self.order()
@@ -5,7 +5,7 @@ Implemenation of same basic algorithms used in cryptography.
5
5
  __author__ = "Gerald Teschl"
6
6
  __copyright__ = "Copyright 2024-2025, Gerald Teschl"
7
7
  __license__ = "MIT License"
8
- __version__ = "1.3.4"
8
+ __version__ = "1.4"
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,326 @@
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
+ if self.H * y: # no code word
118
+ s = self.gf(0) # syndrom polynomial
119
+ for i, x in enumerate(self.alpha):
120
+ s += int(y[i]) * Poly([-x, 1], ring = self.gf, modulus = self.g).inv()
121
+ v = (s.inv() - Poly([0, 1], ring = self.gf, modulus = self.g))**(self.gf.order ** self.g.degree() // 2)
122
+ # determine error locator polynomial sigma(x) = a(x^2) + b(x^2) * x
123
+ # find a and b by running EEA until b has the desired degree
124
+ v.modulus = None
125
+ r0, r1 = self.g, v
126
+ y0, y1 = Poly([self.gf(0)]), Poly([self.gf(1)])
127
+ while 2 * r1.degree() > self.g.degree():
128
+ q, r = r0.divmod(r1)
129
+ r0, r1 = r1, r
130
+ y0, y1 = y1, y0 - q * y1
131
+ a, b = r1, y1
132
+ sigma = a**2 + b**2 * Poly([0,1], ring = self.gf)
133
+ # correct the error
134
+ for x in self.alpha:
135
+ if not sigma(x):
136
+ y[int(x)] += 1
137
+ if self.H * y:
138
+ raise ValueError("Decoding failed!")
139
+ x = self.G.transpose().solve(y)
140
+ x.map(int)
141
+ return list(x)
142
+
143
+ class CyclicCode():
144
+ """
145
+ Cyclic code.
146
+
147
+ Example:
148
+
149
+ >>> n = 4 # length of the code
150
+ >>> g = Poly([1, 1], ring = Zmod(2)) # generating Polynomial for the code
151
+ >>> cc = CyclicCode(n, g)
152
+
153
+ To encode a list of letters (the length must be equal to the dimension k of the code):
154
+ >>> cc.encode([0,1,0], ring = Zmod(2))
155
+ [0, 1, 1, 0]
156
+
157
+ Decoding is only implemnted for code words:
158
+ >>> cc.decode([0, 1, 1, 0], ring = Zmod(2))
159
+ [0, 1, 0]
160
+
161
+ """
162
+
163
+ def __init__(self, n:int, g: Poly):
164
+ if n < 2:
165
+ raise ValueError(f"Code lenght {n} must be at least 2!")
166
+ zero = 0 * g[0]
167
+ try:
168
+ order = zero.ring.n # Zmod
169
+ except:
170
+ try:
171
+ order = zero.field.order # GF2
172
+ except:
173
+ try:
174
+ order = type(zero).order # galois
175
+ except:
176
+ raise ValueError("Unknown base filed!")
177
+ self.order = order # order of the base field
178
+ one = zero**0
179
+ modulus = Poly([-one] + [zero] * (n-1) + [one])
180
+ self.modulus = modulus
181
+ if modulus % g:
182
+ raise ValueError(f"The polynomial g must be a factor of x^{n} - 1!")
183
+ self.n = n # lenght
184
+ self.k = n - g.degree() # dimension
185
+ self.g = g # generating polynomial
186
+ self.h = Poly(modulus) // g
187
+
188
+ def __repr__(self):
189
+ return f"Cyclic [{self.n}, {self.k}] code over GF({self.order}) with generator polynomial g(x) = {self.g}."
190
+
191
+ def encode(self, x: list|Poly, gf = None, systematic_form = False):
192
+ "Encode a given list or polynomial."
193
+ as_list = False
194
+ zero = 0 * self.g[0]
195
+ if isinstance(x, list | tuple):
196
+ as_list = True
197
+ x = Poly(x, ring = gf)
198
+ if x.degree() >= self.k:
199
+ raise ValueError(f"Degree can be at most {self.k-1}.")
200
+ if systematic_form:
201
+ x.coeff = [zero] * self.g.degree() + x.coeff
202
+ y = x - (x % self.g)
203
+ else:
204
+ y = self.g * x
205
+ if as_list:
206
+ return y.coeff + [zero] * (self.n - len(y.coeff))
207
+ return y
208
+
209
+ def decode(self, y: list|Poly, gf = None, systematic_form = False):
210
+ "Decode a given list or polynomial."
211
+ as_list = False
212
+ zero = 0 * self.g[0]
213
+ if isinstance(y, list | tuple):
214
+ as_list = True
215
+ y = Poly(y, ring = gf)
216
+ if y.degree() >= self.n:
217
+ raise ValueError(f"Degree can be at most {self.n-1}.")
218
+ x, r = y.divmod(self.g)
219
+ if r:
220
+ raise NotImplementedError(f"Word is no code word! Cannot decode.")
221
+ if systematic_form:
222
+ x.coeff = y.coeff[self.g.degree():]
223
+ if as_list:
224
+ return x.coeff + [zero] * (self.k - len(x.coeff))
225
+ return x
226
+
227
+ def generator_matrix(self):
228
+ "Return the generator matrix of the code."
229
+ zero = 0 * self.g[0]
230
+ return Matrix([[zero] * j + self.g.coeff + [zero] * (self.k - j - 1 ) for j in range(self.k)])
231
+
232
+ def check_matrix(self):
233
+ "Return the parity check matrix of the code."
234
+ zero = 0 * self.g[0]
235
+ tmp = list(reversed(self.h.coeff))
236
+ return Matrix([[zero] * j + tmp + [zero] * (self.n - self.k - j - 1 ) for j in range(self.n-self.k)])
237
+
238
+ class ReedSolomonCode(CyclicCode):
239
+ """
240
+ Reed-Solomon code.
241
+
242
+ Example:
243
+
244
+ >>> gf = Zmod(13) # Galois field
245
+ >>> k = 3 # dimension of the code
246
+ >>> rsc = ReedSolomonCode(k, gf)
247
+
248
+ 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]
251
+
252
+ Decoding is only implemnted for code words:
253
+ >>> cc.decode([0, 1, 1, 0], ring = Zmod(2))
254
+ [0, 1, 0]
255
+
256
+ """
257
+ def __init__(self, k:int, gf, alpha = None):
258
+ zero = gf(0)
259
+ one = zero**0
260
+ self. gf = gf
261
+ try:
262
+ n = gf.order()
263
+ except:
264
+ n = gf.order - 1
265
+ self.order = n + 1 # order of the base field
266
+ self.n = n # lenght
267
+ self.k = k # dimension
268
+ self.d = n - k - 1 # minimal distance
269
+ self.modulus = Poly([-one] + [zero] * (n-1) + [one])
270
+ if alpha is None:
271
+ try:
272
+ alpha = gf.generator() # Zmod|GF2
273
+ except:
274
+ alpha = gf.primitive_element # galois
275
+ self.alpha = alpha
276
+ self.g = prod([Poly([-alpha**j, one]) for j in range(1,n-k+1)], start = one) # generating polynomial
277
+ self.h = Poly(self.modulus) // self.g # check polynomial
278
+ assert k == n - self.g.degree()
279
+ assert self.g * self.h == self.modulus
280
+
281
+ def __repr__(self):
282
+ return f"Reed-Solomon-Code [{self.n}, {self.k}, {self.d}] over GF({self.n+1})."
283
+
284
+ def encode(self, x: list|Poly):
285
+ "Encode a given list or polynomial."
286
+ as_list = False
287
+ zero = 0 * self.g[0]
288
+ if isinstance(x, list | tuple):
289
+ as_list = True
290
+ x = Poly(x, ring = self.gf)
291
+ if x.degree() >= self.k:
292
+ raise ValueError(f"Degree can be at most {self.k-1}.")
293
+ y = [ x(self.alpha**j) for j in range(self.n) ]
294
+ if as_list:
295
+ return y + [zero] * (self.n - len(y))
296
+ return Poly(y)
297
+
298
+ def decode(self, y: list|Poly):
299
+ "Decode a given list or polynomial."
300
+ as_list = False
301
+ zero = 0 * self.g[0]
302
+ one = zero**0
303
+ if isinstance(y, list | tuple):
304
+ as_list = True
305
+ y = Poly(y, ring = self.gf)
306
+ if y.degree() >= self.n:
307
+ raise ValueError(f"Degree can be at most {self.n-1}.")
308
+ x = Poly([ -y(self.alpha**(-j)) for j in range(self.n) ])
309
+ if x.degree() >= self.k: # No codewort
310
+ t = (self.n - self.k) // 2
311
+ 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) ])
312
+ b = [ -x[self.k + t + l ] for l in range(t) ]
313
+ lam = Poly([one] + list(A.solve(b)))
314
+ x_coordinates = []
315
+ y_coordinates = []
316
+ for j in range(self.n):
317
+ if len(x_coordinates) == self.k:
318
+ break
319
+ alphaj = self.alpha**j
320
+ if lam(alphaj):
321
+ x_coordinates.append(alphaj)
322
+ y_coordinates.append(y[j])
323
+ x = lagrange_interpolation(x_coordinates, y_coordinates)
324
+ if as_list:
325
+ return x.coeff + [zero] * (self.k - len(x.coeff))
326
+ 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)
@@ -6,6 +6,7 @@ Factorization of integers:
6
6
 
7
7
  from math import isqrt, gcd, prod, floor, log10
8
8
  from .primes import sieve_eratosthenes, is_prime
9
+ from .intfuncs import perfect_power
9
10
  from .factor_pm1 import _pm1_parameters, factor_pm1
10
11
  from .factor_ecm import _ecm_parameters, factor_ecm
11
12
  #from .factor_qs import factor_qs
@@ -86,10 +87,16 @@ def factorint(n: int, trial_bnd: int = 2500, verbose: int = 0) -> dict:
86
87
  return prime_factors
87
88
  if verbose:
88
89
  print("Trial division found:", list(prime_factors))
90
+ # check if the remainder is a perfect power
91
+ res = perfect_power(n)
92
+ if res:
93
+ n, k = res
94
+ else:
95
+ n, k = n, 1
89
96
  if is_prime(n, trialdivision=False):
90
- prime_factors[n] = 1
97
+ prime_factors[n] = k
91
98
  return prime_factors
92
- remaining_factors = {n: 1}
99
+ remaining_factors = {n: k}
93
100
 
94
101
  # https://gitlab.inria.fr/zimmerma/ecm/
95
102
  ECM_PARAMETERS = [
@@ -111,7 +111,7 @@ def jacobi_symbol(a: int, n: int) -> int:
111
111
  return 0
112
112
 
113
113
 
114
- def sqrt_mod(a: int, p: int) -> int:
114
+ def sqrt_mod(a: int, p: int) -> int|None:
115
115
  """Compute a square root of `a` modulo `p` unsing Cipolla's algorithm."""
116
116
  a %= p
117
117
  if a in (0, 1):
@@ -120,6 +120,12 @@ def sqrt_mod(a: int, p: int) -> int:
120
120
  return None
121
121
  if p % 4 == 3: # easy case
122
122
  return pow(a, (p + 1) // 4, p)
123
+ if p % 8 == 5:
124
+ x = pow(a, (p + 3)//8, p)
125
+ c = pow(x, 2, p)
126
+ if c % p == a:
127
+ return x
128
+ return (x * pow(2, (p - 1)//4, p)) % p
123
129
  # Cipolla's algorithm
124
130
  for c in range(1, p): # find a field extension
125
131
  tmp = (c * c - a) % p
@@ -2,6 +2,7 @@
2
2
  Polynomials
3
3
  """
4
4
 
5
+ from math import prod
5
6
  from numbers import Number
6
7
  from .primes import is_prime
7
8
  #from .factor import factorint
@@ -407,3 +408,22 @@ class Poly:
407
408
  return False
408
409
  return True
409
410
 
411
+
412
+ def lagrange_interpolation(x_coordinates:list, y_coordinates:list) -> "Poly":
413
+ "Compute the Lagrange interpolation polynomial from a given list of x and y values."
414
+ l = len(x_coordinates)
415
+ if l == 0 or l != len(y_coordinates):
416
+ raise ValueError('List of x and y values must be nonempty of equal length.')
417
+ if l != len(set(x_coordinates)):
418
+ raise ValueError('List of x values must not contain duplicate values.')
419
+ # Guess zero and one
420
+ zero = 0 * x_coordinates[0]
421
+ one = zero**0
422
+
423
+ poly = Poly([zero])
424
+ L_all = prod([Poly([-x, one]) for x in x_coordinates], start = one)
425
+
426
+ for x, y in zip(x_coordinates, y_coordinates):
427
+ L = L_all // Poly([-x, one])
428
+ poly += (y / prod([x -xx for xx in x_coordinates if xx != x], start = one)) * L
429
+ return poly
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryptools
3
- Version: 1.3.4
3
+ Version: 1.4
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, irreducibility test, 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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryptools"
3
- version = "1.3.4"
3
+ version = "1.4"
4
4
  authors = [
5
5
  { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" },
6
6
  ]
@@ -87,6 +87,26 @@ def test_ZmodP_order():
87
87
  xx *=x
88
88
  assert o == x.order()
89
89
 
90
+ def test_ZmodP_sqrt():
91
+ for n in range(1,100):
92
+ print(n)
93
+ ring = Zmod(n)
94
+ one = ring(1)
95
+ roots = {} # compute a dict of all roots
96
+ for x in ring:
97
+ y = x**2
98
+ if y in roots:
99
+ roots[y].append(x)
100
+ else:
101
+ roots[y] = [ x ]
102
+ for x in ring:
103
+ if x not in roots:
104
+ with pytest.raises(ValueError):
105
+ x.sqrt()
106
+ else:
107
+ y = x.sqrt()
108
+ assert y**2 == x
109
+
90
110
  def test_Zmod_field():
91
111
  for n in range(1, 100):
92
112
  assert Zmod(n).is_field() == is_prime(n)
@@ -1,7 +1,7 @@
1
1
  import pytest
2
2
  from random import randint, seed
3
3
  from kryptools import Zmod, GF2, Poly, Matrix
4
- from kryptools import gen2pchk, Goppa
4
+ from kryptools import gen2pchk, Goppa, CyclicCode, ReedSolomonCode
5
5
 
6
6
  seed(0)
7
7
  Z_2 = Zmod(2)
@@ -39,3 +39,24 @@ def test_Goppa():
39
39
  i = randint(0, goppa.G.cols-1)
40
40
  y[i] = 1- y[i]
41
41
  assert goppa.decode(y) == x
42
+
43
+ def test_Cyclic():
44
+ for gf, n, g in [ [Zmod(2), 4, [1, 1]], [Zmod(3), 4, [-1, 1]], [Zmod(11), 5, [4, 6, 1]] ]:
45
+ g = Poly(g, ring = gf)
46
+ cc = CyclicCode(n, g)
47
+ for _ in range(num_tests):
48
+ x = [ gf(randint(0, cc.order-1)) for _ in range(cc.k) ]
49
+ y = cc.encode(x)
50
+ assert cc.decode(y) == x
51
+
52
+ def test_ReedSolomon():
53
+ for gf, k in [ [Zmod(13), 3], [GF2(6), 4]]:
54
+ rsc = ReedSolomonCode(k, gf)
55
+ t = (rsc.n - rsc.k) // 2
56
+ for _ in range(num_tests):
57
+ x = [ gf(randint(0, rsc.n)) for _ in range(rsc.k) ]
58
+ y = rsc.encode(x)
59
+ for _ in range(t):
60
+ i = randint(0, rsc.n-1)
61
+ y[i] += gf(1)
62
+ assert rsc.decode(y) == x
@@ -1,5 +1,6 @@
1
1
  import pytest
2
- from kryptools import Poly, Zmod, GF2
2
+ from random import sample, choices
3
+ from kryptools import Poly, Zmod, GF2, lagrange_interpolation
3
4
  from kryptools import divisors, moebius_mu
4
5
 
5
6
 
@@ -33,4 +34,16 @@ def test_rabin():
33
34
  a = Poly(c, ring=gf)
34
35
  if a.rabin_test():
35
36
  count += 1
36
- assert count == sum([moebius_mu(d) * order ** (t // d) for d in divisors(t)]) // t
37
+ assert count == sum([moebius_mu(d) * order ** (t // d) for d in divisors(t)]) // t
38
+
39
+ def test_lagrange():
40
+ for gf in [ Zmod(2), Zmod(3), Zmod(5), Zmod(7), Zmod(23), GF2(4), GF2(6)]:
41
+ all = list(gf)
42
+ l = len(all)//2
43
+ for _ in range(10):
44
+ x_coordinates = sample(all, l)
45
+ y_coordinates = choices(all, k = l)
46
+ p = lagrange_interpolation(x_coordinates, y_coordinates)
47
+ assert p.degree() <= len(x_coordinates) - 1
48
+ for x, y in zip(x_coordinates, y_coordinates):
49
+ assert p(x) == y
@@ -1,140 +0,0 @@
1
- """
2
- Linear codes
3
- """
4
-
5
- from .Zmod import Zmod
6
- from .GF2 import GF2
7
- from .poly import Poly
8
- from .la import Matrix, eye, zeros
9
-
10
-
11
- def poly2bits(x: Poly) -> list:
12
- "Convert a polynomial in GF(2^n)[x] to a list of binary digits."
13
- out = []
14
- for c in x.coeff:
15
- out += c.bits()
16
- return out
17
-
18
- def left_standard_form(M: Matrix) -> (Matrix, Matrix):
19
- "Compute the left standard form of a generator matrix. Return the standard form and the pertmutation matrix."
20
- # reduced row echelon form
21
- M = M.rref()
22
- # purge zero rows
23
- for i in range(M.rows-1,-1,-1):
24
- if not M[i,:]:
25
- M.delete_rows(i)
26
- # permute columns to get the identity on the left
27
- last = min(M.rows, M.cols)
28
- P = eye(M.cols)
29
- for i in range(last):
30
- if not M[i,i]: # the diagonal entry vanishes
31
- # find the index of the pivot and permute
32
- j = i + 1
33
- while j <= M.cols and not M[i, j]:
34
- j += 1
35
- P[:,i], P[:,j] = P[:,j], P[:,i]
36
- M[:,i], M[:,j] = M[:,j], M[:,i]
37
- return M, P
38
-
39
- def gen2pchk(G: Matrix) -> Matrix:
40
- "Compute the parity check matrix from a given generator matrix (and vice versa)."
41
- G, P = left_standard_form(G)
42
- d = G.cols - G.rows
43
- if d <= 0:
44
- raise ValueError("A generator matrix must have more columns than rows!")
45
- zero = 0 * G[0]
46
- one = zero**0
47
- # we start with a zero matrix
48
- H = zeros(d, G.cols, zero = zero)
49
- # create the identity on the right
50
- for i in range(d):
51
- H[i,G.rows+i] = one
52
- # add minus the transpose of the right part of G as the left part
53
- tmp = - G[:,-d:].transpose()
54
- for i in range(G.cols - d):
55
- H[:, i] = tmp[:, i]
56
- return H * P.transpose()
57
-
58
- def hamming_dist(a: iter, b: iter):
59
- "Hamming distance of two iterables."
60
- if len(a) != len(b):
61
- raise ValueError("Both iteralbles must have equal length.")
62
- return sum(aa != bb for aa, bb in zip(a, b))
63
-
64
- class Goppa():
65
- """
66
- Binary irreducible Goppa code.
67
-
68
- Example:
69
-
70
- >>> gf = GF2(4) # base field GF(2^n)
71
- >>> g = Poly([2, 0, 0, 1], ring = gf) # irreducible Polynomial for the code
72
- >>> alpha = list(gf) # list of points from the base field (must not contain zeros of g)
73
- >>> goppa = Goppa(gf, g, alpha)
74
-
75
- To encode a list of bits (the length must be equal to the dimension k of the code)
76
- >>> goppa.encode([1, 0, 1, 1])
77
- [0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1]
78
-
79
- Decoding still works if there are at most deg(g) errors:
80
- >>> goppa.decode([0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0])
81
- [1, 0, 1, 1]
82
-
83
- """
84
-
85
- def __init__(self, gf: GF2, g: Poly, alpha: list["GF2Point"]):
86
- if not all(map(lambda x: x in gf, alpha)):
87
- raise ValueError(f"{x}: alpha must be a list of elements from the Galois field!")
88
- if not g.rabin_test():
89
- raise ValueError(f"The polynomial g must be irreducible!")
90
- self.gf = gf # underlying Galois field
91
- self.g = g # polynomial
92
- self.alpha = alpha # list of elements from gf
93
- # Compute the control matrix
94
- self.Z_2 = Zmod(2)
95
-
96
- tmp =[]
97
- for x in alpha:
98
- tmp.append(Poly([-x, 1], ring = gf, modulus = g).inv().bits())
99
- self.H = Matrix(tmp, ring=self.Z_2).transpose()
100
- # Compute the generator matrix
101
- self.G = gen2pchk(self.H)
102
-
103
- def __repr__(self):
104
- return f"Binary irreducible Goppa code over GF(2^{self.gf.power}) with polynomial g(x) = {self.g}."
105
-
106
- def encode(self, x: list[int]):
107
- "Encode a given list of bits."
108
- x = Matrix(x, ring = self.Z_2)
109
- y = self.G.transpose() * x
110
- y.map(int)
111
- return list(y)
112
-
113
- def decode(self, y: list[int]):
114
- "Decode a given list of bits."
115
- y = Matrix(y, ring = self.Z_2)
116
- if self.H * y: # no code word
117
- s = self.gf(0) # syndrom polynomial
118
- for i, x in enumerate(self.alpha):
119
- s += int(y[i]) * Poly([-x, 1], ring = self.gf, modulus = self.g).inv()
120
- v = (s.inv() - Poly([0, 1], ring = self.gf, modulus = self.g))**(self.gf.order ** self.g.degree() // 2)
121
- # determine error locator polynomial sigma(x) = a(x^2) + b(x^2) * x
122
- # find a and b by running EEA until b has the desired degree
123
- v.modulus = None
124
- r0, r1 = self.g, v
125
- y0, y1 = Poly([self.gf(0)]), Poly([self.gf(1)])
126
- while 2 * r1.degree() > self.g.degree():
127
- q, r = r0.divmod(r1)
128
- r0, r1 = r1, r
129
- y0, y1 = y1, y0 - q * y1
130
- a, b = r1, y1
131
- sigma = a**2 + b**2 * Poly([0,1], ring = self.gf)
132
- # correct the error
133
- for x in self.alpha:
134
- if not sigma(x):
135
- y[int(x)] += 1
136
- if self.H * y:
137
- raise ValueError("Decoding failed!")
138
- x = self.G.transpose().solve(y)
139
- x.map(int)
140
- return list(x)
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