kryptools 1.5.3__tar.gz → 1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kryptools-1.5.3 → kryptools-1.6}/PKG-INFO +3 -2
- {kryptools-1.5.3 → kryptools-1.6}/README.md +2 -1
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/GF2.py +50 -30
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/Zmod.py +8 -5
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/__init__.py +2 -2
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/code.py +82 -50
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_qs.py +2 -2
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/la.py +54 -2
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/poly.py +22 -4
- {kryptools-1.5.3 → kryptools-1.6}/kryptools.egg-info/PKG-INFO +3 -2
- {kryptools-1.5.3 → kryptools-1.6}/pyproject.toml +1 -1
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_GF2.py +12 -1
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_Zmod.py +5 -2
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_code.py +33 -1
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_la.py +14 -1
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_poly.py +2 -0
- {kryptools-1.5.3 → kryptools-1.6}/LICENSE +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/aes.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/blockcipher.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/des.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp_qs.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/ec.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_dix.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/factor_rho.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/intfuncs.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/keccak.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/lat.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/nt.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/primes.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools/sha1.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/setup.cfg +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_aes.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_des.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_dlog.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_ec.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_factor.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_factor_qs.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_intfuncs.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_keccak.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_lat.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_nt.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_primes.py +0 -0
- {kryptools-1.5.3 → kryptools-1.6}/tests/test_sha1.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
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
|
|
94
|
+
return GF2nPoint(0, self)
|
|
91
95
|
|
|
92
96
|
def one(self) -> bool:
|
|
93
97
|
"Return one."
|
|
94
98
|
if self.bitreversed:
|
|
95
|
-
return
|
|
96
|
-
return
|
|
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([
|
|
102
|
-
return
|
|
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(
|
|
119
|
+
return self.one()
|
|
116
120
|
for a in range(2, self.order):
|
|
117
|
-
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
|
|
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
|
-
|
|
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
|
|
167
|
-
|
|
168
|
-
else:
|
|
169
|
-
coeff = reversed([int(d) for d in str(
|
|
170
|
-
format(self.x, "0" + str(self.field.degree) + "b"))])
|
|
171
|
-
return list(coeff)
|
|
177
|
+
return coeff
|
|
178
|
+
return list(reversed(coeff))
|
|
172
179
|
|
|
173
180
|
def poly(self) -> "Poly":
|
|
174
181
|
"Convert to a polynomial."
|
|
@@ -193,7 +200,7 @@ class GF2nPoint:
|
|
|
193
200
|
return NotImplemented
|
|
194
201
|
if self.field != other.field:
|
|
195
202
|
raise NotImplementedError("Cannot add elements from different fields.")
|
|
196
|
-
return self.
|
|
203
|
+
return self.__class__(self.x ^ other.x, self.field)
|
|
197
204
|
|
|
198
205
|
__sub__ = __add__
|
|
199
206
|
|
|
@@ -206,7 +213,7 @@ class GF2nPoint:
|
|
|
206
213
|
if isinstance(other, int):
|
|
207
214
|
if other % 2:
|
|
208
215
|
return self
|
|
209
|
-
return self.
|
|
216
|
+
return self.__class__(0, self.field)
|
|
210
217
|
if not isinstance(other, self.__class__):
|
|
211
218
|
return NotImplemented
|
|
212
219
|
if self.field != other.field:
|
|
@@ -232,13 +239,13 @@ class GF2nPoint:
|
|
|
232
239
|
y <<= 1
|
|
233
240
|
if y & bitmap1:
|
|
234
241
|
y ^= self.field.modulus
|
|
235
|
-
return self.
|
|
242
|
+
return self.__class__(z, self.field)
|
|
236
243
|
|
|
237
244
|
def __rmul__(self, scalar: int) -> "GF2nPoint":
|
|
238
245
|
if isinstance(scalar, int):
|
|
239
246
|
if scalar % 2:
|
|
240
247
|
return self
|
|
241
|
-
return self.
|
|
248
|
+
return self.__class__(0, self.field)
|
|
242
249
|
return NotImplemented
|
|
243
250
|
|
|
244
251
|
def __truediv__(self, other: "GF2nPoint") -> "GF2nPoint":
|
|
@@ -255,14 +262,16 @@ class GF2nPoint:
|
|
|
255
262
|
return NotImplemented
|
|
256
263
|
if not (self.x) and j < 0:
|
|
257
264
|
raise ValueError("Division by zero.")
|
|
265
|
+
j %= self.field.order - 1
|
|
258
266
|
if self.field.order == 2:
|
|
267
|
+
if j == 0:
|
|
268
|
+
return self.field.one()
|
|
259
269
|
return self
|
|
260
270
|
if self.field.bitreversed:
|
|
261
|
-
res = self.
|
|
271
|
+
res = self.__class__(self.field.order >> 1, self.field)
|
|
262
272
|
else:
|
|
263
|
-
res = self.
|
|
264
|
-
x = self.
|
|
265
|
-
j %= self.field.order - 1
|
|
273
|
+
res = self.__class__(1, self.field)
|
|
274
|
+
x = self.__class__(self.x, self.field)
|
|
266
275
|
while j > 0:
|
|
267
276
|
# If j is odd, multiply with x
|
|
268
277
|
if j & 1:
|
|
@@ -276,13 +285,13 @@ class GF2nPoint:
|
|
|
276
285
|
"Cyclic rotation to the left."
|
|
277
286
|
x = self.x << j
|
|
278
287
|
x = (x % self.field.order) + (x // self.field.order)
|
|
279
|
-
return self.
|
|
288
|
+
return self.__class__(self.x, self.field)
|
|
280
289
|
|
|
281
290
|
def __rshift__(self, j: int) -> "GF2nPoint":
|
|
282
291
|
"Cyclic rotation to the right."
|
|
283
292
|
x = self.x >> j
|
|
284
293
|
x += (self.field.order >> j) * (self.x % 2**j)
|
|
285
|
-
return self.
|
|
294
|
+
return self.__class__(self.x, self.field)
|
|
286
295
|
|
|
287
296
|
def sqrt(self) -> "GF2nPoint":
|
|
288
297
|
"Compute the square root."
|
|
@@ -308,6 +317,17 @@ class GF2nPoint:
|
|
|
308
317
|
"Test if the point is a generator of the group GF(2^n)^*."
|
|
309
318
|
return self.field.mult_order == self.order()
|
|
310
319
|
|
|
320
|
+
def minpoly(self) -> Poly:
|
|
321
|
+
"Return the minimal polynomial of an element."
|
|
322
|
+
aj = self
|
|
323
|
+
mpoly = Poly([aj, self.field.one()])
|
|
324
|
+
for _ in range(1, self.field.degree):
|
|
325
|
+
aj = aj**2 # Frobenius
|
|
326
|
+
if aj == self:
|
|
327
|
+
break
|
|
328
|
+
mpoly *= Poly([aj, self.field.one()])
|
|
329
|
+
return mpoly
|
|
330
|
+
|
|
311
331
|
def sbox(self, inv: bool = False) -> "GF2nPoint":
|
|
312
332
|
"Apply the AES sbox."
|
|
313
333
|
if self.field.degree == 8 and self.field.modulus == 0b100011011: # AES
|
|
@@ -43,8 +43,8 @@ class Zmod:
|
|
|
43
43
|
|
|
44
44
|
def __call__(self, x: int | list | tuple | range | map):
|
|
45
45
|
if isinstance(x, list|tuple|range|map):
|
|
46
|
-
return [ZmodPoint(xx, self) for xx in x]
|
|
47
|
-
return ZmodPoint(x, self)
|
|
46
|
+
return [ZmodPoint(int(xx), self) for xx in x]
|
|
47
|
+
return ZmodPoint(int(x), self)
|
|
48
48
|
|
|
49
49
|
def __iter__(self):
|
|
50
50
|
for x in range(self.n):
|
|
@@ -157,7 +157,7 @@ class ZmodPoint:
|
|
|
157
157
|
"Represents a point in the ring Zmod."
|
|
158
158
|
|
|
159
159
|
def __init__(self, x: int, ring: "Zmod"):
|
|
160
|
-
self.x =
|
|
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
|
-
|
|
251
|
-
|
|
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.
|
|
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
|
|
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
|
|
9
|
-
from .la import Matrix
|
|
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(
|
|
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
|
-
|
|
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
|
|
43
|
-
d = G.cols - G.rows
|
|
44
|
-
if d <= 0:
|
|
45
|
-
raise ValueError("A generator matrix must have more columns than rows!")
|
|
46
|
-
zero = 0 * G[0]
|
|
47
|
-
one = zero**0
|
|
48
|
-
# we start with a zero matrix
|
|
49
|
-
H = zeros(d, G.cols, zero = zero)
|
|
50
|
-
# create the identity on the right
|
|
51
|
-
for i in range(d):
|
|
52
|
-
H[i,G.rows+i] = one
|
|
53
|
-
# add minus the transpose of the right part of G as the left part
|
|
54
|
-
tmp = - G[:,-d:].transpose()
|
|
55
|
-
for i in range(G.cols - d):
|
|
56
|
-
H[:, i] = tmp[:, i]
|
|
57
|
-
return H * P.transpose()
|
|
25
|
+
return G.kernel().transpose()
|
|
58
26
|
|
|
59
27
|
def hamming_dist(a: iter, b: iter):
|
|
60
28
|
"Hamming distance of two iterables."
|
|
@@ -217,13 +185,18 @@ class CyclicCode():
|
|
|
217
185
|
raise ValueError(f"Degree can be at most {self.n-1}.")
|
|
218
186
|
x, r = y.divmod(self.g)
|
|
219
187
|
if r:
|
|
220
|
-
|
|
188
|
+
y = self.correct(y)
|
|
189
|
+
x, r = y.divmod(self.g)
|
|
221
190
|
if systematic_form:
|
|
222
191
|
x.coeff = y.coeff[self.g.degree():]
|
|
223
192
|
if as_list:
|
|
224
193
|
return x.coeff + [zero] * (self.k - len(x.coeff))
|
|
225
194
|
return x
|
|
226
195
|
|
|
196
|
+
def correct(self, y: Poly):
|
|
197
|
+
"Correct a given polynomial."
|
|
198
|
+
raise NotImplementedError("Error correction not implemented.")
|
|
199
|
+
|
|
227
200
|
def generator_matrix(self):
|
|
228
201
|
"Return the generator matrix of the code."
|
|
229
202
|
zero = 0 * self.g[0]
|
|
@@ -249,7 +222,7 @@ class ReedSolomonCode(CyclicCode):
|
|
|
249
222
|
>>> rsc.encode([1, 2, 3])
|
|
250
223
|
[6, 4, 5, 1, 8, 4, 2, 9, 2, 8, 9, 6]
|
|
251
224
|
|
|
252
|
-
To
|
|
225
|
+
To decode use:
|
|
253
226
|
>>> rsc.decode([1, 4, 4, 0, 8, 4, 2, 9, 2, 0, 9, 6])
|
|
254
227
|
[1, 2, 3]
|
|
255
228
|
|
|
@@ -265,7 +238,9 @@ class ReedSolomonCode(CyclicCode):
|
|
|
265
238
|
self.order = n + 1 # order of the base field
|
|
266
239
|
self.n = n # lenght
|
|
267
240
|
self.k = k # dimension
|
|
268
|
-
|
|
241
|
+
if k >= n:
|
|
242
|
+
raise ValueError(f"Code dimension k = {k} must be smaller than the code lenght n = {self.n}.")
|
|
243
|
+
self.d = n - k + 1 # minimal distance
|
|
269
244
|
self.modulus = Poly([-one] + [zero] * (n-1) + [one])
|
|
270
245
|
if alpha is None:
|
|
271
246
|
if hasattr(gf, "primitive_element"):
|
|
@@ -309,19 +284,76 @@ class ReedSolomonCode(CyclicCode):
|
|
|
309
284
|
x = Poly(xx)
|
|
310
285
|
if x.degree() >= self.k: # No codewort
|
|
311
286
|
t = (self.n - self.k) // 2
|
|
287
|
+
if not t:
|
|
288
|
+
raise ValueError("This code cannot correct any errors.")
|
|
312
289
|
A = Matrix([[ xx[j - l % self.n] for l in range(1, t+1) ] for j in range(self.k+t, self.k+2*t) ])
|
|
313
290
|
b = [ -xx[self.k + t + l ] for l in range(t) ]
|
|
314
291
|
lam = Poly([one] + list(A.solve(b)))
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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) ->
|
|
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) ->
|
|
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)
|
|
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)
|
|
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=
|
|
67
|
+
def __repr__(self, latex=None):
|
|
67
68
|
def prx(i: int) -> str:
|
|
68
69
|
if i == 0:
|
|
69
70
|
return ""
|
|
70
71
|
if i == 1:
|
|
71
72
|
return self.__class__.print_x
|
|
72
73
|
if latex:
|
|
74
|
+
if i < 10:
|
|
75
|
+
return self.__class__.print_x + "^" + str(i)
|
|
73
76
|
return self.__class__.print_x + "^{" + str(i) + "}"
|
|
74
77
|
return self.__class__.print_x + self.__class__.print_pow + str(i)
|
|
75
78
|
|
|
79
|
+
if latex is None:
|
|
80
|
+
latex = self.__class__.print_latex
|
|
76
81
|
if not self:
|
|
77
82
|
return str(self.coeff[0])
|
|
78
83
|
one = self.coeff[0]**0
|
|
@@ -298,7 +303,7 @@ class Poly:
|
|
|
298
303
|
return self.__class__([zero]), self
|
|
299
304
|
div = [zero] * (sd - od + 1)
|
|
300
305
|
lco = other.coeff[-1]
|
|
301
|
-
if
|
|
306
|
+
if lco != one:
|
|
302
307
|
tmp = one / lco
|
|
303
308
|
oth = [c * tmp for c in other.coeff]
|
|
304
309
|
rem = [c * tmp for c in self.coeff]
|
|
@@ -310,7 +315,7 @@ class Poly:
|
|
|
310
315
|
div[sd - od - i] = tmp
|
|
311
316
|
for j in range(od + 1):
|
|
312
317
|
rem[sd - i - j] -= tmp * oth[od - j]
|
|
313
|
-
if
|
|
318
|
+
if lco != one:
|
|
314
319
|
rem = [c * lco for c in rem]
|
|
315
320
|
return self.__class__(div, modulus=self.modulus), self.__class__(rem, modulus=self.modulus)
|
|
316
321
|
|
|
@@ -327,7 +332,7 @@ class Poly:
|
|
|
327
332
|
if sd < od:
|
|
328
333
|
return self
|
|
329
334
|
lco = other.coeff[-1]
|
|
330
|
-
if
|
|
335
|
+
if lco != one:
|
|
331
336
|
tmp = one / lco
|
|
332
337
|
oth = [c * tmp for c in other.coeff]
|
|
333
338
|
else:
|
|
@@ -363,12 +368,15 @@ class Poly:
|
|
|
363
368
|
if r0.degree() != 0:
|
|
364
369
|
raise ValueError(f"{self} is not invertible mod {other}.")
|
|
365
370
|
y0.modulus = self.modulus
|
|
371
|
+
if r0.coeff[0] == one:
|
|
372
|
+
return y0
|
|
366
373
|
return y0 / r0.coeff[0]
|
|
367
374
|
|
|
368
375
|
def gcd(self, other: "Poly") -> "Poly":
|
|
369
376
|
"Greates common divisor with a given polynomial."
|
|
370
377
|
if not isinstance(other, self.__class__):
|
|
371
378
|
raise NotImplementedError(f"Cannot compute gcd: {other} must be a polynomial.")
|
|
379
|
+
_, one, _ = self._guess_ring()
|
|
372
380
|
if not self:
|
|
373
381
|
if not other:
|
|
374
382
|
return Poly([self.coeff[0]])
|
|
@@ -379,8 +387,14 @@ class Poly:
|
|
|
379
387
|
while r1:
|
|
380
388
|
r0, r1 = r1, r0.divmod(r1)[1]
|
|
381
389
|
r0.modulus = None
|
|
390
|
+
if r0.coeff[-1] == one:
|
|
391
|
+
return r0
|
|
382
392
|
return r0 / r0.coeff[-1]
|
|
383
393
|
|
|
394
|
+
def lcm(self, other: "Poly") -> "Poly":
|
|
395
|
+
"Least common multiple with a given polynomial."
|
|
396
|
+
return self * other // self.gcd(other)
|
|
397
|
+
|
|
384
398
|
def egcd(self, other: "Poly") -> ("Poly", "Poly", "Poly"):
|
|
385
399
|
"""Perform the extended Euclidean agorithm for polynomials. Returns gcd, x, y such that other * x + self * y = gcd."""
|
|
386
400
|
zero, one, _ = self._guess_ring()
|
|
@@ -389,6 +403,8 @@ class Poly:
|
|
|
389
403
|
if not self:
|
|
390
404
|
if not other:
|
|
391
405
|
return Poly([zero]), Poly([zero]), Poly([zero])
|
|
406
|
+
if other.coeff[-1] == one:
|
|
407
|
+
return other, Poly([zero]), one
|
|
392
408
|
return other / other.coeff[-1], Poly([zero]), one / other.coeff[-1]
|
|
393
409
|
r0, r1 = other, self
|
|
394
410
|
x0, x1 = self.__class__([one]), self.__class__([zero])
|
|
@@ -399,6 +415,8 @@ class Poly:
|
|
|
399
415
|
x0, x1 = x1, x0 - x1 * q
|
|
400
416
|
y0, y1 = y1, y0 - y1 * q
|
|
401
417
|
lcr0 = r0.coeff[-1]
|
|
418
|
+
if lcr0 == one:
|
|
419
|
+
return r0, x0, y0
|
|
402
420
|
return r0 / lcr0, x0 / lcr0, y0 / lcr0
|
|
403
421
|
|
|
404
422
|
def derivative(self) -> "Poly":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
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
|
|
@@ -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 =
|
|
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 [
|
|
45
|
-
assert [abs(
|
|
44
|
+
assert [i.sharp() for i in Z_5] == [0, 1, 2, -2, -1]
|
|
45
|
+
assert [abs(i) for i in Z_5] == [0, 1, 2, 2, 1]
|
|
46
46
|
assert not Z_5(0)
|
|
47
47
|
assert Z_5(1)
|
|
48
48
|
assert Z_5(2) == Z_5(2)
|
|
@@ -63,6 +63,9 @@ def test_Zmod_methods():
|
|
|
63
63
|
assert Z_6(5).is_generator() is True
|
|
64
64
|
assert list(Z_6.generators()) == Z_6([5])
|
|
65
65
|
assert list(Z_6.star()) == Z_6([1, 5])
|
|
66
|
+
assert [i.sharp() for i in Z_6] == [0, 1, 2, 3, -2, -1]
|
|
67
|
+
assert [abs(i) for i in Z_6] == [0, 1, 2, 3, 2, 1]
|
|
68
|
+
assert not Z_6(0)
|
|
66
69
|
assert Z_6(1)
|
|
67
70
|
assert Z_6(2) == Z_6(2)
|
|
68
71
|
assert Z_6(2) != Z_6(3)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import pytest # pylint: disable=W0611
|
|
3
3
|
from random import randint, seed # pylint: disable=C0411
|
|
4
4
|
from kryptools import Zmod, GF2, Poly, Matrix
|
|
5
|
-
from kryptools import gen2pchk, Goppa, CyclicCode, ReedSolomonCode
|
|
5
|
+
from kryptools import gen2pchk, Goppa, CyclicCode, ReedSolomonCode, BCHCode
|
|
6
6
|
|
|
7
7
|
seed(0)
|
|
8
8
|
Z_2 = Zmod(2)
|
|
@@ -33,6 +33,7 @@ def test_Goppa():
|
|
|
33
33
|
alpha = list(gf)
|
|
34
34
|
|
|
35
35
|
goppa = Goppa(gf, g, alpha)
|
|
36
|
+
assert not goppa.H * goppa.G.transpose()
|
|
36
37
|
for _ in range(num_tests):
|
|
37
38
|
x = [ randint(0,1) for _ in range(goppa.G.rows) ]
|
|
38
39
|
y = goppa.encode(x)
|
|
@@ -45,6 +46,12 @@ def test_Cyclic():
|
|
|
45
46
|
for gf, n, g in [ [Zmod(2), 4, [1, 1]], [Zmod(3), 4, [-1, 1]], [Zmod(11), 5, [4, 6, 1]] ]:
|
|
46
47
|
g = Poly(g, ring = gf)
|
|
47
48
|
cc = CyclicCode(n, g)
|
|
49
|
+
G = cc.generator_matrix()
|
|
50
|
+
H = cc.check_matrix()
|
|
51
|
+
assert not H * G.transpose()
|
|
52
|
+
H2 = gen2pchk(G)
|
|
53
|
+
assert H2.rows == H.rows
|
|
54
|
+
assert not H2 * G.transpose()
|
|
48
55
|
for _ in range(num_tests):
|
|
49
56
|
x = [ gf(randint(0, cc.order-1)) for _ in range(cc.k) ]
|
|
50
57
|
y = cc.encode(x)
|
|
@@ -53,6 +60,12 @@ def test_Cyclic():
|
|
|
53
60
|
def test_ReedSolomon():
|
|
54
61
|
for gf, k in [ [Zmod(13), 3], [GF2(6), 4]]:
|
|
55
62
|
rsc = ReedSolomonCode(k, gf)
|
|
63
|
+
G = rsc.generator_matrix()
|
|
64
|
+
H = rsc.check_matrix()
|
|
65
|
+
assert not H * G.transpose()
|
|
66
|
+
H2 = gen2pchk(G)
|
|
67
|
+
assert H2.rows == H.rows
|
|
68
|
+
assert not H2 * G.transpose()
|
|
56
69
|
t = (rsc.n - rsc.k) // 2
|
|
57
70
|
for _ in range(num_tests):
|
|
58
71
|
x = [ gf(randint(0, rsc.n)) for _ in range(rsc.k) ]
|
|
@@ -61,3 +74,22 @@ def test_ReedSolomon():
|
|
|
61
74
|
i = randint(0, rsc.n-1)
|
|
62
75
|
y[i] += gf(1)
|
|
63
76
|
assert rsc.decode(y) == x
|
|
77
|
+
|
|
78
|
+
def test_BCH():
|
|
79
|
+
gf = GF2(1)
|
|
80
|
+
for n, D in [ [7, 3], [15, 5]]:
|
|
81
|
+
bch = BCHCode(n, D)
|
|
82
|
+
G = bch.generator_matrix()
|
|
83
|
+
H = bch.check_matrix()
|
|
84
|
+
assert not H * G.transpose()
|
|
85
|
+
H2 = gen2pchk(G)
|
|
86
|
+
assert H2.rows == H.rows
|
|
87
|
+
assert not H2 * G.transpose()
|
|
88
|
+
t = (D - 1) // 2
|
|
89
|
+
for _ in range(num_tests):
|
|
90
|
+
x = [ gf(randint(0, 1)) for _ in range(bch.k) ]
|
|
91
|
+
y = bch.encode(x)
|
|
92
|
+
for _ in range(t):
|
|
93
|
+
i = randint(0, bch.n-1)
|
|
94
|
+
y[i] += gf(1)
|
|
95
|
+
assert bch.decode(y) == x
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# pragma pylint: disable=C0114,C0116
|
|
2
2
|
import pytest # pylint: disable=W0611
|
|
3
3
|
from fractions import Fraction # pylint: disable=C0411
|
|
4
|
-
from kryptools import Matrix, Zmod
|
|
4
|
+
from kryptools import Matrix, Zmod, GF2
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def test_Matrix():
|
|
@@ -26,3 +26,16 @@ def test_Matrix():
|
|
|
26
26
|
M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 12]], ring=Z_11)
|
|
27
27
|
Mi = M.inv()
|
|
28
28
|
assert M * Mi == M.eye()
|
|
29
|
+
|
|
30
|
+
def test_kernel():
|
|
31
|
+
for gf in Zmod(2), Zmod(7), GF2(1), GF2(8):
|
|
32
|
+
for m,n in ((3, 5), (5, 3), (3, 3)):
|
|
33
|
+
for _ in range(25):
|
|
34
|
+
A = Matrix([[gf.random() for _ in range(m)] for _ in range(n)], ring = gf)
|
|
35
|
+
K = A.kernel()
|
|
36
|
+
assert not A * K
|
|
37
|
+
assert K.rows == A.cols
|
|
38
|
+
if K:
|
|
39
|
+
assert K.cols == A.nullity()
|
|
40
|
+
else:
|
|
41
|
+
assert K.cols == 1
|
|
@@ -18,6 +18,7 @@ def test_Poly():
|
|
|
18
18
|
assert q.degree() == 3
|
|
19
19
|
assert q.weight() == 4
|
|
20
20
|
d, m = q.divmod(p)
|
|
21
|
+
assert q * p == p.gcd(q) * p.lcm(q)
|
|
21
22
|
assert d * p + m == q
|
|
22
23
|
for _ in range(num_tests):
|
|
23
24
|
p = Poly([randint(0,100) for _ in range(10)])
|
|
@@ -33,6 +34,7 @@ def test_Poly():
|
|
|
33
34
|
q = Poly([1, 2, 3, 4], ring = gf)
|
|
34
35
|
d, m = q.divmod(p)
|
|
35
36
|
assert d * p + m == q
|
|
37
|
+
assert q * p == p.gcd(q) * p.lcm(q)
|
|
36
38
|
for _ in range(num_tests):
|
|
37
39
|
p = Poly(gf.random(10))
|
|
38
40
|
q = Poly(gf.random(12))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|