kryptools 1.4__tar.gz → 1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kryptools-1.4 → kryptools-1.5}/PKG-INFO +2 -2
- {kryptools-1.4 → kryptools-1.5}/README.md +1 -1
- {kryptools-1.4 → kryptools-1.5}/kryptools/GF2.py +29 -17
- {kryptools-1.4 → kryptools-1.5}/kryptools/Zmod.py +22 -3
- {kryptools-1.4 → kryptools-1.5}/kryptools/__init__.py +1 -1
- {kryptools-1.4 → kryptools-1.5}/kryptools/code.py +7 -5
- {kryptools-1.4 → kryptools-1.5}/kryptools/poly.py +189 -65
- {kryptools-1.4 → kryptools-1.5}/kryptools.egg-info/PKG-INFO +2 -2
- {kryptools-1.4 → kryptools-1.5}/pyproject.toml +1 -1
- {kryptools-1.4 → kryptools-1.5}/tests/test_GF2.py +1 -1
- {kryptools-1.4 → kryptools-1.5}/tests/test_poly.py +16 -2
- {kryptools-1.4 → kryptools-1.5}/LICENSE +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/aes.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/blockcipher.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/conway_polynomials.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/des.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/dlp.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/dlp_bsgs.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/dlp_ic.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/dlp_qs.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/dlp_rho.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/ec.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/factor.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/factor_dix.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/factor_ecm.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/factor_fmt.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/factor_pm1.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/factor_qs.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/factor_rho.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/intfuncs.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/keccak.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/la.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/lat.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/nt.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/primes.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools/sha1.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools.egg-info/SOURCES.txt +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools.egg-info/dependency_links.txt +0 -0
- {kryptools-1.4 → kryptools-1.5}/kryptools.egg-info/top_level.txt +0 -0
- {kryptools-1.4 → kryptools-1.5}/setup.cfg +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_Zmod.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_aes.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_code.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_des.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_dlog.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_ec.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_factor.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_factor_fmt.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_factor_pm1.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_factor_qs.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_intfuncs.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_keccak.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_la.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_lat.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_nt.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_primes.py +0 -0
- {kryptools-1.4 → kryptools-1.5}/tests/test_sha1.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5
|
|
4
4
|
Summary: Implemenation of same basic algorithms used in cryptography.
|
|
5
5
|
Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
|
|
6
6
|
License: MIT
|
|
@@ -42,7 +42,7 @@ 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, irreducibility test, Lagrange interpolation)
|
|
45
|
+
* Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
|
|
46
46
|
* Zmod: a class for the ring of integers modulo an integer
|
|
47
47
|
* GF2: a class for Galois fields GF(2^n)
|
|
48
48
|
* Goppa: a class for encoding and decoding Goppa codes
|
|
@@ -24,7 +24,7 @@ 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, irreducibility test, Lagrange interpolation)
|
|
27
|
+
* Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
|
|
28
28
|
* Zmod: a class for the ring of integers modulo an integer
|
|
29
29
|
* GF2: a class for Galois fields GF(2^n)
|
|
30
30
|
* Goppa: a class for encoding and decoding Goppa codes
|
|
@@ -3,6 +3,7 @@ Galois field GF(2^n).
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from math import gcd
|
|
6
|
+
from random import randint
|
|
6
7
|
from .poly import Poly
|
|
7
8
|
from .Zmod import Zmod
|
|
8
9
|
from .factor import factorint
|
|
@@ -41,13 +42,14 @@ class GF2:
|
|
|
41
42
|
if n in conway_polynomials2:
|
|
42
43
|
modulus = conway_polynomials2[n]
|
|
43
44
|
else:
|
|
44
|
-
raise ValueError("Unknown
|
|
45
|
+
raise ValueError("Unknown degree. Please specify a modulus.")
|
|
45
46
|
self.modulus = modulus # integer whose biniary digits are the coefficients of the irreducible modulus polynomial
|
|
46
47
|
if modulus & 2**n == 2**n:
|
|
47
48
|
self.bitreversed = False
|
|
48
49
|
else:
|
|
49
50
|
self.bitreversed = True
|
|
50
|
-
self.
|
|
51
|
+
self.degree = n # n
|
|
52
|
+
self.characteristic = 2
|
|
51
53
|
self.order = 2**n # 2**n
|
|
52
54
|
self.print_hex: bool = True
|
|
53
55
|
self.byteorder: str = "big" # big|little
|
|
@@ -55,7 +57,7 @@ class GF2:
|
|
|
55
57
|
self.factors = {} # factoring of the group order
|
|
56
58
|
|
|
57
59
|
def __repr__(self):
|
|
58
|
-
return f"GF(2^{self.
|
|
60
|
+
return f"GF(2^{self.degree})"
|
|
59
61
|
|
|
60
62
|
def __call__(self, x: int | list | tuple | range | map):
|
|
61
63
|
if isinstance(x, list|tuple|range|map):
|
|
@@ -74,7 +76,7 @@ class GF2:
|
|
|
74
76
|
return self.order == other.order and self.modulus == other.modulus
|
|
75
77
|
return False
|
|
76
78
|
|
|
77
|
-
def __contains__(self, other: "
|
|
79
|
+
def __contains__(self, other: "GF2nPoint") -> bool:
|
|
78
80
|
return isinstance(other, GF2nPoint) and self == other.field
|
|
79
81
|
|
|
80
82
|
def _factors(self) -> dict:
|
|
@@ -83,12 +85,22 @@ class GF2:
|
|
|
83
85
|
self.factors = factorint(self.mult_order)
|
|
84
86
|
return self.factors
|
|
85
87
|
|
|
88
|
+
def zero(self) -> bool:
|
|
89
|
+
"Return zero."
|
|
90
|
+
return self(0)
|
|
91
|
+
|
|
86
92
|
def one(self) -> bool:
|
|
87
93
|
"Return one."
|
|
88
94
|
if self.bitreversed:
|
|
89
95
|
return self(self.order - 1)
|
|
90
96
|
return self(1)
|
|
91
97
|
|
|
98
|
+
def random(self, num: int = 0) -> "GF2nPoint":
|
|
99
|
+
"Return a single random point or a list of random points."
|
|
100
|
+
if num:
|
|
101
|
+
return([self(randint(0,self.order-1)) for _ in range(num)])
|
|
102
|
+
return self(randint(0,self.order-1))
|
|
103
|
+
|
|
92
104
|
def is_cyclic(self) -> bool:
|
|
93
105
|
"GF(2^n)^* is cyclic."
|
|
94
106
|
return True
|
|
@@ -99,7 +111,7 @@ class GF2:
|
|
|
99
111
|
|
|
100
112
|
def generator(self) -> int | None:
|
|
101
113
|
"Return a generator of the group GF(2^n)^*."
|
|
102
|
-
if self.
|
|
114
|
+
if self.degree == 1:
|
|
103
115
|
return self(1)
|
|
104
116
|
for a in range(2, self.order):
|
|
105
117
|
a = self(a)
|
|
@@ -131,7 +143,7 @@ class GF2nPoint:
|
|
|
131
143
|
|
|
132
144
|
def __repr__(self):
|
|
133
145
|
if self.field.print_hex:
|
|
134
|
-
return format(self.x, "0"+str(self.field.
|
|
146
|
+
return format(self.x, "0"+str(self.field.degree // 4)+"x")
|
|
135
147
|
return self.x
|
|
136
148
|
|
|
137
149
|
def __eq__(self, other):
|
|
@@ -146,16 +158,16 @@ class GF2nPoint:
|
|
|
146
158
|
return self.x
|
|
147
159
|
|
|
148
160
|
def __bytes__(self):
|
|
149
|
-
return self.x.to_bytes(self.field.
|
|
161
|
+
return self.x.to_bytes(self.field.degree // 8, byteorder=self.field.byteorder)
|
|
150
162
|
|
|
151
163
|
def bits(self) -> list:
|
|
152
164
|
"Convert to a list of bits."
|
|
153
165
|
if self.field.bitreversed:
|
|
154
166
|
coeff = [int(d) for d in str(
|
|
155
|
-
format(self.x, "0" + str(self.field.
|
|
167
|
+
format(self.x, "0" + str(self.field.degree) + "b"))]
|
|
156
168
|
else:
|
|
157
169
|
coeff = reversed([int(d) for d in str(
|
|
158
|
-
format(self.x, "0" + str(self.field.
|
|
170
|
+
format(self.x, "0" + str(self.field.degree) + "b"))])
|
|
159
171
|
return list(coeff)
|
|
160
172
|
|
|
161
173
|
def poly(self) -> "Poly":
|
|
@@ -163,14 +175,14 @@ class GF2nPoint:
|
|
|
163
175
|
Z_2 = Zmod(2)
|
|
164
176
|
if self.field.bitreversed:
|
|
165
177
|
coeff = [Z_2(int(d)) for d in str(
|
|
166
|
-
format(self.x, "0" + str(self.field.
|
|
178
|
+
format(self.x, "0" + str(self.field.degree) + "b"))]
|
|
167
179
|
coeff_modulus = [Z_2(int(d)) for d in str(
|
|
168
|
-
format((self.field.modulus << 1) + 1, "0" + str(self.field.
|
|
180
|
+
format((self.field.modulus << 1) + 1, "0" + str(self.field.degree) + "b"))]
|
|
169
181
|
else:
|
|
170
182
|
coeff = reversed([Z_2(int(d)) for d in str(
|
|
171
|
-
format(self.x, "0" + str(self.field.
|
|
183
|
+
format(self.x, "0" + str(self.field.degree) + "b"))])
|
|
172
184
|
coeff_modulus = reversed([Z_2(int(d)) for d in str(
|
|
173
|
-
format(self.field.modulus, "0" + str(self.field.
|
|
185
|
+
format(self.field.modulus, "0" + str(self.field.degree) + "b"))])
|
|
174
186
|
return Poly(list(coeff), modulus=list(coeff_modulus))
|
|
175
187
|
|
|
176
188
|
def __hash__(self):
|
|
@@ -208,7 +220,7 @@ class GF2nPoint:
|
|
|
208
220
|
z = 0
|
|
209
221
|
y = other.x
|
|
210
222
|
if self.field.bitreversed:
|
|
211
|
-
for d in bin(self.x)[2:].zfill(self.field.
|
|
223
|
+
for d in bin(self.x)[2:].zfill(self.field.degree):
|
|
212
224
|
if int(d):
|
|
213
225
|
z ^= y
|
|
214
226
|
if y % 2:
|
|
@@ -219,7 +231,7 @@ class GF2nPoint:
|
|
|
219
231
|
else:
|
|
220
232
|
bitmap1 = self.field.order
|
|
221
233
|
bitmap2 = 1
|
|
222
|
-
for _ in range(self.field.
|
|
234
|
+
for _ in range(self.field.degree):
|
|
223
235
|
if self.x & bitmap2:
|
|
224
236
|
z ^= y
|
|
225
237
|
bitmap2 <<= 1
|
|
@@ -300,11 +312,11 @@ class GF2nPoint:
|
|
|
300
312
|
|
|
301
313
|
def sbox(self, inv: bool = False) -> "GF2nPoint":
|
|
302
314
|
"Apply the AES sbox."
|
|
303
|
-
if self.field.
|
|
315
|
+
if self.field.degree == 8 and self.field.modulus == 0b100011011: # AES
|
|
304
316
|
if inv:
|
|
305
317
|
return aes_sbox_inv[self.x]
|
|
306
318
|
return aes_sbox[self.x]
|
|
307
|
-
elif self.field.
|
|
319
|
+
elif self.field.degree == 4 and self.field.modulus == 0b10011: # MiniAES
|
|
308
320
|
if inv:
|
|
309
321
|
return miniaes_sbox_inv[self.x]
|
|
310
322
|
return miniaes_sbox[self.x]
|
|
@@ -3,6 +3,7 @@ Ring of intergers modulo `n`.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from math import gcd
|
|
6
|
+
from random import randint
|
|
6
7
|
from .factor import factorint
|
|
7
8
|
from .primes import is_prime
|
|
8
9
|
from .nt import sqrt_mod, crt
|
|
@@ -31,9 +32,10 @@ class Zmod:
|
|
|
31
32
|
if not isinstance(n, int) or n < 1:
|
|
32
33
|
raise ValueError(f"{n} is not a positive integer.")
|
|
33
34
|
self.n = n
|
|
35
|
+
self.isfield = None
|
|
34
36
|
self.n_factors = {} # factoring of n
|
|
35
37
|
self.short = short
|
|
36
|
-
self.group_order = 0
|
|
38
|
+
self.group_order = 0 # order of the multiplicative group
|
|
37
39
|
self.factors = {} # factoring of the multiplicative group order
|
|
38
40
|
|
|
39
41
|
def __repr__(self):
|
|
@@ -56,6 +58,12 @@ class Zmod:
|
|
|
56
58
|
def __contains__(self, other: "ZmodPoint") -> bool:
|
|
57
59
|
return isinstance(other, ZmodPoint) and self.n == other.ring.n
|
|
58
60
|
|
|
61
|
+
def random(self, num: int = 0) -> "ZmodPoint":
|
|
62
|
+
"Return a single random point or a list of random points."
|
|
63
|
+
if num:
|
|
64
|
+
return([self(randint(0,self.n-1)) for _ in range(num)])
|
|
65
|
+
return self(randint(0,self.n-1))
|
|
66
|
+
|
|
59
67
|
def order(self) -> int:
|
|
60
68
|
"Compute the order of the group Z_n^*."
|
|
61
69
|
if self.group_order:
|
|
@@ -63,7 +71,7 @@ class Zmod:
|
|
|
63
71
|
# We compute euler_phi(n) and its factorization in one pass
|
|
64
72
|
if not self.n_factors:
|
|
65
73
|
self.n_factors = factorint(self.n)
|
|
66
|
-
for p, k in self.n_factors.items(): #
|
|
74
|
+
for p, k in self.n_factors.items(): # factorization of n
|
|
67
75
|
# factor p-1 and add the factors
|
|
68
76
|
for pm, km in factorint(p - 1).items():
|
|
69
77
|
if pm in self.factors:
|
|
@@ -96,7 +104,18 @@ class Zmod:
|
|
|
96
104
|
|
|
97
105
|
def is_field(self) -> bool:
|
|
98
106
|
"Test if Z_n is a field."
|
|
99
|
-
|
|
107
|
+
if self.isfield is None:
|
|
108
|
+
if self.n_factors:
|
|
109
|
+
if len(self.n_factors) == 1:
|
|
110
|
+
self.isfield = True
|
|
111
|
+
else:
|
|
112
|
+
self.isfield = False
|
|
113
|
+
elif is_prime(self.n):
|
|
114
|
+
self.isfield = True
|
|
115
|
+
self.n_factors = { self.n: 1}
|
|
116
|
+
else:
|
|
117
|
+
self.isfield = False
|
|
118
|
+
return self.isfield
|
|
100
119
|
|
|
101
120
|
def generator(self) -> int | None:
|
|
102
121
|
"Return a generator of the group Z_n^*."
|
|
@@ -5,7 +5,7 @@ Implemenation of same basic algorithms used in cryptography.
|
|
|
5
5
|
__author__ = "Gerald Teschl"
|
|
6
6
|
__copyright__ = "Copyright 2024-2025, Gerald Teschl"
|
|
7
7
|
__license__ = "MIT License"
|
|
8
|
-
__version__ = "1.
|
|
8
|
+
__version__ = "1.5"
|
|
9
9
|
__email__ = "Gerald.Teschl@univie.ac.at"
|
|
10
10
|
|
|
11
11
|
from .nt import egcd, cf, convergents, legendre_symbol, jacobi_symbol, sqrt_mod, euler_phi, carmichael_lambda, moebius_mu, is_carmichael_number, order, crt
|
|
@@ -114,22 +114,24 @@ class Goppa():
|
|
|
114
114
|
def decode(self, y: list[int]):
|
|
115
115
|
"Decode a given list of bits."
|
|
116
116
|
y = Matrix(y, ring = self.Z_2)
|
|
117
|
+
zero = self.gf(0)
|
|
118
|
+
one = zero**0
|
|
117
119
|
if self.H * y: # no code word
|
|
118
|
-
s =
|
|
120
|
+
s = zero # syndrom polynomial
|
|
119
121
|
for i, x in enumerate(self.alpha):
|
|
120
|
-
s += int(y[i]) * Poly([-x,
|
|
121
|
-
v = (s.inv() - Poly([
|
|
122
|
+
s += int(y[i]) * Poly([-x, one], modulus = self.g).inv()
|
|
123
|
+
v = (s.inv() - Poly([zero, one], modulus = self.g))**(self.gf.order ** self.g.degree() // 2)
|
|
122
124
|
# determine error locator polynomial sigma(x) = a(x^2) + b(x^2) * x
|
|
123
125
|
# find a and b by running EEA until b has the desired degree
|
|
124
126
|
v.modulus = None
|
|
125
127
|
r0, r1 = self.g, v
|
|
126
|
-
y0, y1 = Poly([
|
|
128
|
+
y0, y1 = Poly([zero]), Poly([one])
|
|
127
129
|
while 2 * r1.degree() > self.g.degree():
|
|
128
130
|
q, r = r0.divmod(r1)
|
|
129
131
|
r0, r1 = r1, r
|
|
130
132
|
y0, y1 = y1, y0 - q * y1
|
|
131
133
|
a, b = r1, y1
|
|
132
|
-
sigma = a**2 + b**2 * Poly([
|
|
134
|
+
sigma = a**2 + b**2 * Poly([zero, one])
|
|
133
135
|
# correct the error
|
|
134
136
|
for x in self.alpha:
|
|
135
137
|
if not sigma(x):
|
|
@@ -4,6 +4,7 @@ Polynomials
|
|
|
4
4
|
|
|
5
5
|
from math import prod
|
|
6
6
|
from numbers import Number
|
|
7
|
+
from random import randint
|
|
7
8
|
from .primes import is_prime
|
|
8
9
|
#from .factor import factorint
|
|
9
10
|
|
|
@@ -24,7 +25,7 @@ class Poly:
|
|
|
24
25
|
|
|
25
26
|
def __init__(self, coeff: list, ring=None, modulus: list = None):
|
|
26
27
|
self.coeff = list(coeff)
|
|
27
|
-
for i in range(len(self.coeff) - 1, 0, -1):
|
|
28
|
+
for i in range(len(self.coeff) - 1, 0, -1): # strip leading zeros
|
|
28
29
|
if self.coeff[i]:
|
|
29
30
|
break
|
|
30
31
|
self.coeff.pop(i)
|
|
@@ -49,6 +50,9 @@ class Poly:
|
|
|
49
50
|
def __len__(self):
|
|
50
51
|
return len(self.coeff)
|
|
51
52
|
|
|
53
|
+
def __hash__(self):
|
|
54
|
+
return hash(tuple(self.coeff))
|
|
55
|
+
|
|
52
56
|
def __repr__(self, latex = False):
|
|
53
57
|
def prx(i: int) -> str:
|
|
54
58
|
if i == 0:
|
|
@@ -142,27 +146,34 @@ class Poly:
|
|
|
142
146
|
ring = None
|
|
143
147
|
return zero, one, ring
|
|
144
148
|
|
|
149
|
+
def _guess_field(self):
|
|
150
|
+
zero, one, field = self._guess_ring()
|
|
151
|
+
if not field:
|
|
152
|
+
raise ValueError("The polynomial does not seem to be over a known finite field.")
|
|
153
|
+
if hasattr(field, 'n'): # Zmod
|
|
154
|
+
p = field.n
|
|
155
|
+
q = field.n
|
|
156
|
+
if not field.is_field():
|
|
157
|
+
raise ValueError("The polynomial does not seem to be over a finite field.")
|
|
158
|
+
else: # G2 or galois
|
|
159
|
+
p = field.characteristic
|
|
160
|
+
q = field.order
|
|
161
|
+
return field, zero, one, q, p
|
|
162
|
+
|
|
145
163
|
def __add__(self, other: "Poly") -> "Poly":
|
|
146
|
-
zero = 0 * self.coeff[0]
|
|
147
164
|
if not isinstance(other, self.__class__):
|
|
148
165
|
if self._check_type(other):
|
|
149
166
|
tmp = self.coeff[:]
|
|
150
167
|
tmp[0] += other
|
|
151
168
|
return self.__class__(tmp, modulus=self.modulus)
|
|
152
169
|
return NotImplemented
|
|
153
|
-
ls, lo = len(self.coeff), len(other.coeff)
|
|
154
|
-
if ls < lo:
|
|
155
|
-
scoeff = self.coeff + (lo - ls) * [zero]
|
|
156
|
-
else:
|
|
157
|
-
scoeff = self.coeff
|
|
158
|
-
if ls > lo:
|
|
159
|
-
ocoeff = other.coeff + (ls - lo) * [zero]
|
|
160
|
-
else:
|
|
161
|
-
ocoeff = other.coeff
|
|
162
170
|
modulus = self.modulus
|
|
163
171
|
if not modulus and other.modulus:
|
|
164
172
|
modulus = other.modulus
|
|
165
|
-
|
|
173
|
+
ls, lo = len(self.coeff), len(other.coeff)
|
|
174
|
+
if ls < lo:
|
|
175
|
+
return self.__class__([ s + o for s, o in zip(self.coeff, other.coeff) ] + other.coeff[ls:], modulus=modulus)
|
|
176
|
+
return self.__class__([ s + o for s, o in zip(self.coeff, other.coeff) ] + self.coeff[lo:], modulus=modulus)
|
|
166
177
|
|
|
167
178
|
def __radd__(self, other: "Poly") -> "Poly":
|
|
168
179
|
return self + other
|
|
@@ -186,9 +197,7 @@ class Poly:
|
|
|
186
197
|
return NotImplemented
|
|
187
198
|
zero = 0 * self.coeff[0]
|
|
188
199
|
ls, lo = len(self.coeff), len(other.coeff)
|
|
189
|
-
coeff = [
|
|
190
|
-
for k in range(ls + lo - 1):
|
|
191
|
-
coeff[k] = sum((self.coeff[j] * other.coeff[k - j] for j in range(max(0, k - lo + 1), min(ls, k + 1))), start=zero)
|
|
200
|
+
coeff = [ sum((self.coeff[j] * other.coeff[k - j] for j in range(max(0, k - lo + 1), min(ls, k + 1))), start=zero) for k in range(ls + lo - 1) ]
|
|
192
201
|
modulus = self.modulus
|
|
193
202
|
if not modulus and other.modulus:
|
|
194
203
|
modulus = other.modulus
|
|
@@ -308,7 +317,7 @@ class Poly:
|
|
|
308
317
|
tmp = self.coeff[sd - i] * one # "* 1" is here to make sure we get a copy
|
|
309
318
|
for j in range(od + 1):
|
|
310
319
|
self.coeff[sd - i - j] -= tmp * oth[od - j]
|
|
311
|
-
for i in range(len(self.coeff) - 1, 0, -1):
|
|
320
|
+
for i in range(len(self.coeff) - 1, 0, -1): # strip leading zeros
|
|
312
321
|
if self.coeff[i]:
|
|
313
322
|
break
|
|
314
323
|
self.coeff.pop(i)
|
|
@@ -318,53 +327,60 @@ class Poly:
|
|
|
318
327
|
if not other:
|
|
319
328
|
other = self.modulus
|
|
320
329
|
zero, one, ring = self._guess_ring()
|
|
321
|
-
if isinstance(other,
|
|
322
|
-
other =
|
|
323
|
-
elif not isinstance(other,
|
|
324
|
-
raise NotImplementedError(f"
|
|
330
|
+
if isinstance(other, self.__class__):
|
|
331
|
+
other = other.coeff
|
|
332
|
+
elif not isinstance(other, list):
|
|
333
|
+
raise NotImplementedError(f"{other} must be a list of coefficients or a polynomial.")
|
|
334
|
+
other = Poly(other, ring = ring)
|
|
325
335
|
if not other:
|
|
326
336
|
raise NotImplementedError(f"{other} must be nonzero.")
|
|
327
337
|
r0, r1 = other, self
|
|
328
|
-
y0, y1 = self.__class__([zero]
|
|
338
|
+
y0, y1 = self.__class__([zero]), self.__class__([one])
|
|
329
339
|
while r1:
|
|
330
340
|
q, r = r0.divmod(r1)
|
|
331
341
|
r0, r1 = r1, r
|
|
332
342
|
y0, y1 = y1, y0 - q * y1
|
|
333
343
|
if r0.degree() != 0:
|
|
334
344
|
raise ValueError(f"{self} is not invertible mod {other}.")
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
y0.coeff[i] *= tmp
|
|
338
|
-
return y0
|
|
345
|
+
y0.modulus = self.modulus
|
|
346
|
+
return y0 / r0.coeff[0]
|
|
339
347
|
|
|
340
348
|
def gcd(self, other: "Poly") -> "Poly":
|
|
341
349
|
"Greates common divisor with a given polynomial."
|
|
342
350
|
ring = self._guess_ring()[-1]
|
|
343
|
-
if isinstance(other,
|
|
344
|
-
other = self.__class__(other, ring=ring)
|
|
345
|
-
elif not isinstance(other, self.__class__):
|
|
351
|
+
if not isinstance(other, self.__class__):
|
|
346
352
|
raise NotImplementedError(f"Cannot compute gcd: {other} must be a polynomial.")
|
|
353
|
+
if not self:
|
|
354
|
+
if not other:
|
|
355
|
+
return Poly([self.coeff[0]])
|
|
356
|
+
tmp = other / other.coeff[-1]
|
|
357
|
+
tmp.modulus = None
|
|
358
|
+
return tmp
|
|
347
359
|
r0, r1 = other, self
|
|
348
360
|
while r1:
|
|
349
361
|
r0, r1 = r1, r0.divmod(r1)[1]
|
|
350
|
-
|
|
362
|
+
r0.modulus = None
|
|
363
|
+
return r0 / r0.coeff[-1]
|
|
351
364
|
|
|
352
365
|
def egcd(self, other: "Poly") -> ("Poly", "Poly", "Poly"):
|
|
353
366
|
"""Perform the extended Euclidean agorithm for polynomials. Returns gcd, x, y such that other * x + self * y = gcd."""
|
|
354
367
|
zero, one, ring = self._guess_ring()
|
|
355
|
-
if isinstance(other,
|
|
356
|
-
other = self.__class__(other, ring=ring)
|
|
357
|
-
elif not isinstance(other, self.__class__):
|
|
368
|
+
if not isinstance(other, self.__class__):
|
|
358
369
|
raise NotImplementedError(f"Cannot perform egcd: {other} must be a polynomial.")
|
|
370
|
+
if not self:
|
|
371
|
+
if not other:
|
|
372
|
+
return Poly([zero]), Poly([zero]), Poly([zero])
|
|
373
|
+
return other / other.coeff[-1], Poly([zero]), one / other.coeff[-1]
|
|
359
374
|
r0, r1 = other, self
|
|
360
|
-
x0, x1 = self.__class__([one]
|
|
361
|
-
y0, y1 = self.__class__([zero]
|
|
375
|
+
x0, x1 = self.__class__([one]), self.__class__([zero])
|
|
376
|
+
y0, y1 = self.__class__([zero]), self.__class__([one])
|
|
362
377
|
while r1:
|
|
363
378
|
q, r = r0.divmod(r1)
|
|
364
379
|
r0, r1 = r1, r
|
|
365
|
-
x0, x1 = x1, x0 -
|
|
366
|
-
y0, y1 = y1, y0 -
|
|
367
|
-
|
|
380
|
+
x0, x1 = x1, x0 - x1 * q
|
|
381
|
+
y0, y1 = y1, y0 - y1 * q
|
|
382
|
+
lcr0 = r0.coeff[-1]
|
|
383
|
+
return r0 / lcr0, x0 / lcr0, y0 / lcr0
|
|
368
384
|
|
|
369
385
|
def derivative(self) -> "Poly":
|
|
370
386
|
"Returns the formal derivative."
|
|
@@ -375,39 +391,147 @@ class Poly:
|
|
|
375
391
|
tmp = [ j * self.coeff[j] for j in range(1, l) ]
|
|
376
392
|
return self.__class__(tmp, modulus=self.modulus)
|
|
377
393
|
|
|
378
|
-
def
|
|
379
|
-
"
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
#
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
394
|
+
def square_free_factors(self) -> dict:
|
|
395
|
+
"Determine the square free factors of a polynomial over a Galois field."
|
|
396
|
+
_, zero, _, q, p = self._guess_field()
|
|
397
|
+
pr = q // p # x^pr will be the p'th root of x
|
|
398
|
+
if self.degree() < 1:
|
|
399
|
+
return {}
|
|
400
|
+
if self.degree() == 1:
|
|
401
|
+
return { self / self.coeff[-1]: 1}
|
|
402
|
+
factors = {}
|
|
403
|
+
w = self / self.coeff[-1]
|
|
404
|
+
c = w.gcd(w.derivative())
|
|
405
|
+
w = w // c
|
|
406
|
+
|
|
407
|
+
# first we find all factors which are not a power of p
|
|
408
|
+
i = 1
|
|
409
|
+
while w.degree() > 0:
|
|
410
|
+
y = w.gcd(c)
|
|
411
|
+
fac = w // y
|
|
412
|
+
if fac.degree() > 0:
|
|
413
|
+
factors[fac] = i
|
|
414
|
+
w = y
|
|
415
|
+
c //= y
|
|
416
|
+
i += 1
|
|
417
|
+
# c is now a power of p
|
|
418
|
+
if c.degree() > 0:
|
|
419
|
+
# compute the p'th root of c
|
|
420
|
+
d = c.degree() // p
|
|
421
|
+
root = [ zero ] * (d + 1)
|
|
422
|
+
while d >= 0:
|
|
423
|
+
root[d] = c.coeff[d * p]**pr
|
|
424
|
+
d -= 1
|
|
425
|
+
#assert Poly(root)**p == c, root
|
|
426
|
+
for fac, mult in Poly(root).square_free_factors().items():
|
|
427
|
+
factors[fac] = p * mult
|
|
428
|
+
return factors
|
|
429
|
+
|
|
430
|
+
def is_square_free(self) -> bool:
|
|
431
|
+
"Test if the polynomial over a Galois field is square free."
|
|
432
|
+
multiplicities = self.square_free_factors().values()
|
|
433
|
+
return len(multiplicities) == 1 and multiplicities[0] == 1
|
|
434
|
+
|
|
435
|
+
def distinct_degree_factors(self, test:bool = False) -> bool|dict:
|
|
436
|
+
"Factors the polynomial over a Galois field into distinct products of irredicible factors of the same degree."
|
|
437
|
+
_, zero, one, q, _ = self._guess_field()
|
|
438
|
+
if self.degree() < 1:
|
|
439
|
+
raise ValueError("The polynomial must be non-constant.")
|
|
440
|
+
factors = {}
|
|
441
|
+
w = self
|
|
442
|
+
x = Poly([zero, one]) # x
|
|
443
|
+
xq = Poly(q * [zero] + [one], modulus = self.coeff) # x^q modulo self to keep the polynomials small
|
|
444
|
+
k = 1
|
|
445
|
+
while k <= w.degree()//2:
|
|
402
446
|
if k == 1:
|
|
403
447
|
b = xq
|
|
404
448
|
else:
|
|
405
449
|
b **= q # we compute x^{q^k} mod a recursively
|
|
406
|
-
g =
|
|
450
|
+
g = w.gcd(b - x) # gcd with x^{q^k} - x
|
|
407
451
|
if g.degree():
|
|
408
|
-
|
|
409
|
-
|
|
452
|
+
if test:
|
|
453
|
+
return False
|
|
454
|
+
factors[k] = g
|
|
455
|
+
w = w // factors[k]
|
|
456
|
+
k += 1
|
|
457
|
+
if test:
|
|
458
|
+
return True
|
|
459
|
+
if w.degree() > 0:
|
|
460
|
+
factors[w.degree()] = w
|
|
461
|
+
return factors
|
|
462
|
+
|
|
463
|
+
def rabin_test(self) -> bool:
|
|
464
|
+
"Determine if a polynomial is irreducible over a Galois field using a Rabin test."
|
|
465
|
+
return self.distinct_degree_factors(test = True)
|
|
466
|
+
|
|
467
|
+
def is_irreducible(self) -> bool:
|
|
468
|
+
"Test if the polynomial over a Galois field is irreducibel."
|
|
469
|
+
if self.degree() == 0:
|
|
470
|
+
return False
|
|
471
|
+
return self.distinct_degree_factors(test = True)
|
|
410
472
|
|
|
473
|
+
def equal_degree_factors(self ,k:int) -> list:
|
|
474
|
+
"Factors a square free product of irreducibel polynomials of degree `k` over a Galois field."
|
|
475
|
+
field, zero, one, q, p = self._guess_field()
|
|
476
|
+
if hasattr(field, "degree"):
|
|
477
|
+
degree= field.degree
|
|
478
|
+
else:
|
|
479
|
+
degree = 1
|
|
480
|
+
d = self.degree()
|
|
481
|
+
if not isinstance(k, int) or k < 1 :
|
|
482
|
+
raise ValueError(f"The degree {k} must be a positive integer.")
|
|
483
|
+
if d < k or d % k:
|
|
484
|
+
raise ValueError(f"The polynomial is no product of irreducibel polynomials of degree {k}.")
|
|
485
|
+
r = d // k
|
|
486
|
+
qk = (q**k - 1) // 2
|
|
487
|
+
poly = [0] * k + [1]
|
|
488
|
+
|
|
489
|
+
factors = [ self ]
|
|
490
|
+
while len(factors) < r:
|
|
491
|
+
g = Poly([randint(0,q-1) for _ in range(d+1)], ring = field, modulus = self)
|
|
492
|
+
if p == 2: # Gathen-Shoup
|
|
493
|
+
h = g
|
|
494
|
+
for i in range(1,k):
|
|
495
|
+
g += h**q
|
|
496
|
+
if degree > 1:
|
|
497
|
+
h = g
|
|
498
|
+
for i in range(1,degree):
|
|
499
|
+
g += g**2
|
|
500
|
+
else: # Cantor–Zassenhaus
|
|
501
|
+
g **= qk
|
|
502
|
+
factors2 = [] # list for the next round (to avoid the the new factors are tested a second time with the same g)
|
|
503
|
+
for fac in factors:
|
|
504
|
+
if fac.degree() == k: # this one is already irreducible
|
|
505
|
+
factors2.append(fac)
|
|
506
|
+
continue
|
|
507
|
+
gg = fac.gcd(g)
|
|
508
|
+
if gg.degree() == 0 or gg.degree() == fac.degree(): # trivial factor
|
|
509
|
+
factors2.append(fac)
|
|
510
|
+
continue
|
|
511
|
+
gg.modulus = None
|
|
512
|
+
factors2.append(gg)
|
|
513
|
+
factors2.append(fac // gg)
|
|
514
|
+
factors = factors2
|
|
515
|
+
return factors
|
|
516
|
+
|
|
517
|
+
def factor(self) -> dict:
|
|
518
|
+
"Factors a polynomials over a Galois field."
|
|
519
|
+
if self.degree() < 1:
|
|
520
|
+
return {self: 1}
|
|
521
|
+
c = Poly([self.coeff[-1]])
|
|
522
|
+
one = c**0
|
|
523
|
+
if c != one:
|
|
524
|
+
factors = {c: 1}
|
|
525
|
+
else:
|
|
526
|
+
factors = {}
|
|
527
|
+
for fac1, m in self.square_free_factors().items():
|
|
528
|
+
for k, fac2 in fac1.distinct_degree_factors().items():
|
|
529
|
+
if k == fac2.degree(): # irreducible
|
|
530
|
+
factors[fac2] = m
|
|
531
|
+
else:
|
|
532
|
+
for fac3 in fac2.equal_degree_factors(k):
|
|
533
|
+
factors[fac3] = m
|
|
534
|
+
return dict(sorted(factors.items(), key=lambda item: item[0].degree()))
|
|
411
535
|
|
|
412
536
|
def lagrange_interpolation(x_coordinates:list, y_coordinates:list) -> "Poly":
|
|
413
537
|
"Compute the Lagrange interpolation polynomial from a given list of x and y values."
|
|
@@ -416,7 +540,7 @@ def lagrange_interpolation(x_coordinates:list, y_coordinates:list) -> "Poly":
|
|
|
416
540
|
raise ValueError('List of x and y values must be nonempty of equal length.')
|
|
417
541
|
if l != len(set(x_coordinates)):
|
|
418
542
|
raise ValueError('List of x values must not contain duplicate values.')
|
|
419
|
-
#
|
|
543
|
+
# guess zero and one
|
|
420
544
|
zero = 0 * x_coordinates[0]
|
|
421
545
|
one = zero**0
|
|
422
546
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kryptools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5
|
|
4
4
|
Summary: Implemenation of same basic algorithms used in cryptography.
|
|
5
5
|
Author-email: Gerald Teschl <gerald.teschl@univie.ac.at>
|
|
6
6
|
License: MIT
|
|
@@ -42,7 +42,7 @@ 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, irreducibility test, Lagrange interpolation)
|
|
45
|
+
* Poly: a class for polynomials (division, modulo, factoring and irreducibility test over finite fields, Lagrange interpolation)
|
|
46
46
|
* Zmod: a class for the ring of integers modulo an integer
|
|
47
47
|
* GF2: a class for Galois fields GF(2^n)
|
|
48
48
|
* Goppa: a class for encoding and decoding Goppa codes
|
|
@@ -13,7 +13,7 @@ def test_GF2_ops():
|
|
|
13
13
|
assert gf(1) == gf(1)
|
|
14
14
|
assert not gf(1) == gf(0)
|
|
15
15
|
assert not gf(0) == 0
|
|
16
|
-
if gf.
|
|
16
|
+
if gf.degree < 17:
|
|
17
17
|
assert len(list(gf)) == gf.order
|
|
18
18
|
assert len(list(gf.star())) == gf.order - 1
|
|
19
19
|
assert len(list(gf.generators())) == euler_phi(gf.order - 1)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from
|
|
2
|
+
from math import prod
|
|
3
|
+
from random import sample, choices, randint, seed
|
|
3
4
|
from kryptools import Poly, Zmod, GF2, lagrange_interpolation
|
|
4
5
|
from kryptools import divisors, moebius_mu
|
|
5
|
-
|
|
6
|
+
seed(0)
|
|
6
7
|
|
|
7
8
|
def test_Poly():
|
|
8
9
|
p = Poly([2, 0, 2])
|
|
@@ -36,6 +37,19 @@ def test_rabin():
|
|
|
36
37
|
count += 1
|
|
37
38
|
assert count == sum([moebius_mu(d) * order ** (t // d) for d in divisors(t)]) // t
|
|
38
39
|
|
|
40
|
+
def test_factor():
|
|
41
|
+
for gf,t in [ [Zmod(7), 14], [GF2(4), 11]]:
|
|
42
|
+
order = len(list(gf))
|
|
43
|
+
one = Poly([gf(1)])
|
|
44
|
+
for _ in range(20):
|
|
45
|
+
p = one
|
|
46
|
+
while p.degree() < 1:
|
|
47
|
+
p = Poly([randint(0,order-1) for _ in range(t)], ring =gf)
|
|
48
|
+
factors = p.factor()
|
|
49
|
+
assert prod([f**k for f,k in factors.items()], start = one) == p, p
|
|
50
|
+
for fac in factors.keys():
|
|
51
|
+
assert fac.degree() == 0 or fac.rabin_test(), p
|
|
52
|
+
|
|
39
53
|
def test_lagrange():
|
|
40
54
|
for gf in [ Zmod(2), Zmod(3), Zmod(5), Zmod(7), Zmod(23), GF2(4), GF2(6)]:
|
|
41
55
|
all = list(gf)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|