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.
- {kryptools-1.3.4 → kryptools-1.4}/PKG-INFO +4 -2
- {kryptools-1.3.4 → kryptools-1.4}/README.md +3 -1
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/GF2.py +2 -2
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/Zmod.py +59 -5
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/__init__.py +3 -3
- kryptools-1.4/kryptools/code.py +326 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp_qs.py +1 -1
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor.py +9 -2
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/nt.py +7 -1
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/poly.py +20 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools.egg-info/PKG-INFO +4 -2
- {kryptools-1.3.4 → kryptools-1.4}/pyproject.toml +1 -1
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_Zmod.py +20 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_code.py +22 -1
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_poly.py +15 -2
- kryptools-1.3.4/kryptools/code.py +0 -140
- {kryptools-1.3.4 → kryptools-1.4}/LICENSE +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/aes.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/blockcipher.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/des.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/ec.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_dix.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_qs.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/factor_rho.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/intfuncs.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/keccak.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/la.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/lat.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/primes.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools/sha1.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/setup.cfg +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_GF2.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_aes.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_des.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_dlog.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_ec.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_factor.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_factor_qs.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_intfuncs.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_keccak.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_la.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_lat.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_nt.py +0 -0
- {kryptools-1.3.4 → kryptools-1.4}/tests/test_primes.py +0 -0
- {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
|
+
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
|
-
|
|
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.
|
|
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
|
|
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] =
|
|
97
|
+
prime_factors[n] = k
|
|
91
98
|
return prime_factors
|
|
92
|
-
remaining_factors = {n:
|
|
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
|
+
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
|
|
@@ -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
|
|
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
|
|
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
|