kryptools 0.1__py3-none-any.whl

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.
@@ -0,0 +1,140 @@
1
+ """
2
+ Integer factorization: Lentra's ECM
3
+ """
4
+
5
+ from math import gcd, isqrt, log
6
+ from random import randint, seed
7
+ from .primes import sieve_eratosthenes
8
+
9
+ # Crandall and Pomerance: Primes (doi=10.1007/0-387-28979-8)
10
+ # Algorithm 7.2.7
11
+
12
+
13
+ def dbl(P, c2, p):
14
+ # c2 = c - 2
15
+ t1 = (P[0] + P[1]) % p
16
+ t2 = (P[0] - P[1]) % p
17
+ t3 = P[0] * P[1] % p
18
+ return pow(t1 * t2, 2, p), ((t1 * t1 + c2 * t3) % p) * 4 * t3 % p
19
+
20
+
21
+ def add(P1, P2, P3, p):
22
+ t1 = pow(P1[0] * P2[0] - P1[1] * P2[1], 2, p)
23
+ t2 = pow(P1[0] * P2[1] - P2[0] * P1[1], 2, p)
24
+ return P3[1] * t1 % p, P3[0] * t2 % p
25
+
26
+
27
+ def mult(k, P, c2, p):
28
+ if k == 1:
29
+ return P
30
+ if k == 2:
31
+ return dbl(P, c2, p)
32
+ Q = dbl(P, c2, p)
33
+ R = P
34
+ for i in bin(k)[3:]:
35
+ if i == "1":
36
+ R = add(Q, R, P, p)
37
+ Q = dbl(Q, c2, p)
38
+ else:
39
+ Q = add(R, Q, P, p)
40
+ R = dbl(R, c2, p)
41
+ return R
42
+
43
+ # Crandall and Pomerance: Primes (doi=10.1007/0-387-28979-8)
44
+ # Algorithm 7.4.4 (Inversionless ECM)
45
+
46
+ def _ecm_parameters(B1: int, B2: int = None, D: int = None, primes: tuple = None):
47
+ "Precompute parameters for the ECM method."
48
+
49
+ # Stage-one/two limits must be even
50
+ B1 += B1 & 1
51
+ if not B2:
52
+ B2 = 100 * B1
53
+ else:
54
+ B2 += B2 & 1
55
+ if not D:
56
+ D = isqrt(B2)
57
+ if not primes:
58
+ primes = sieve_eratosthenes(B1 - 1 + ((B2 - B1 + 1) // (2 * D) + 1) * 2 * D)
59
+
60
+ stage_one = 1
61
+ for p in primes:
62
+ if p > B1:
63
+ break
64
+ stage_one *= p**int(log(B1, p))
65
+
66
+ stage_two_deltas = {}
67
+ r = B1 - 1
68
+ stage_two_deltas[r] = []
69
+ for p in primes:
70
+ if p <= r:
71
+ continue
72
+ if p <= r + 2 * D:
73
+ stage_two_deltas[r].append((p - r) // 2)
74
+ else:
75
+ r += 2 * D
76
+ while p > r + 2 * D:
77
+ stage_two_deltas[r] = []
78
+ r += 2 * D
79
+ stage_two_deltas[r] = [(p - r) // 2]
80
+ return D, stage_one, stage_two_deltas
81
+
82
+
83
+ def factor_ecm(n: int, B1: int = 11000, B2: int = 1900000, curves: int = 74, ecm_parameters: tuple = None):
84
+ "Factors a number n using Lentsta's ECM method."
85
+
86
+ if ecm_parameters:
87
+ curves, D, stage_one, stage_two_deltas = ecm_parameters
88
+ else:
89
+ D, stage_one, stage_two_deltas = _ecm_parameters(B1, B2)
90
+
91
+ for _ in range(curves):
92
+ # find a random curve
93
+ sigma = randint(6, n - 1)
94
+ u = (sigma**2 - 5) % n
95
+ v = (4 * sigma) % n
96
+ try:
97
+ c = (pow(v - u, 3, n) * (3 * u + v) * pow(4 * u**3 * v, -1, n) - 2) % n
98
+ except:
99
+ m = gcd(4 * u**3 * v, n)
100
+ return m
101
+ c2 = c - 2 % n
102
+ # and a point on the curve (or its twist)
103
+ Q = (pow(u, 3, n), pow(v, 3, n))
104
+ # Stage one
105
+ Q = mult(stage_one, Q, c2, n)
106
+ if Q[1] == 0:
107
+ continue
108
+ g = gcd(Q[1], n)
109
+ if g > 1:
110
+ return g
111
+
112
+ # Stage two
113
+ S = [dbl(Q, c2, n)]
114
+ S.append(dbl(S[0], c2, n))
115
+
116
+ for i in range(2, D):
117
+ S.append(add(S[i - 1], S[0], S[i - 2], n))
118
+
119
+ beta = []
120
+ for i in range(D):
121
+ beta.append((S[i][0] * S[i][1]) % n)
122
+ g = 1
123
+ T = mult(B1 - 2 * D - 1, Q, c2, n)
124
+ R = mult(B1 - 1, Q, c2, n)
125
+ for r in range(B1 - 1, B2, 2 * D):
126
+ alpha = R[0] * R[1] % n
127
+ for delta in stage_two_deltas[r]:
128
+ g = g * ((R[0] - S[delta - 1][0]) * (R[1] + S[delta - 1][1]) - alpha + beta[delta - 1]) % n
129
+ if g == 0:
130
+ break
131
+ if g == 0:
132
+ break
133
+ g =gcd(g,n)
134
+ if 1 < g:
135
+ return g
136
+ R, T = add(R, S[D - 1], T, n), R
137
+
138
+ g = gcd(g, n)
139
+ if 1 < g < n:
140
+ return g
@@ -0,0 +1,58 @@
1
+ """
2
+ Integer factorization: Pollard's p-1 method
3
+ """
4
+
5
+ from math import gcd, log
6
+ from .primes import sieve_eratosthenes
7
+
8
+ def _pm1_parameters(B1: int, B2: int = None, primes: tuple = None):
9
+ "Precompute parameters for the P-1 method."
10
+ if not B2:
11
+ B2 = 100 * B1
12
+ if not primes:
13
+ primes = sieve_eratosthenes(B2)
14
+
15
+ stage_one = 1
16
+ for p in primes:
17
+ if p > B1:
18
+ break
19
+ stage_one *= p**int(log(B1, p))
20
+
21
+ stage_two_deltas = []
22
+ for q in primes:
23
+ if q <= B1:
24
+ continue
25
+ if q > B2:
26
+ break
27
+ stage_two_deltas.append(q - p)
28
+
29
+ return stage_one, stage_two_deltas
30
+
31
+
32
+ def factor_pm1(n: int, B1: int = 11000, B2: int = 1900000, x: int = 2, pm1_parameters: tuple = None):
33
+ "Factors a number n using Pollard's P-1 method."
34
+
35
+ if pm1_parameters:
36
+ stage_one, stage_two_deltas = pm1_parameters
37
+ else:
38
+ stage_one, stage_two_deltas = _pm1_parameters(B1, B2)
39
+
40
+ g = gcd(pow(x, stage_one, n) - 1, n)
41
+ if 1 < g < n:
42
+ return g
43
+ k = 0
44
+ for d in stage_two_deltas:
45
+ saved = {}
46
+ if not d in saved:
47
+ y = pow(x, d, n)
48
+ saved[d] = y
49
+ x = (x * saved[d]) % n
50
+ k += 1
51
+ if k % 20 == 0:
52
+ g = gcd(x-1, n)
53
+ if 1 < g < n:
54
+ return g
55
+ g = gcd(x-1, n)
56
+ if 1 < g < n:
57
+ return g
58
+ return None
kryptools/factor_qs.py ADDED
@@ -0,0 +1,174 @@
1
+ """
2
+ Integer factorization: Quadratic sieve
3
+ """
4
+
5
+ from math import isqrt, gcd, sqrt, log, exp, ceil
6
+ from .primes import sieve_eratosthenes
7
+ from .nt import legendre_symbol, sqrt_mod
8
+
9
+ def bytexor(a: bytearray, b: bytes) -> bytes:
10
+ """Xor the bytestring b to the bytearry a."""
11
+ for i in range(len(a)):
12
+ a[i] ^= b[i]
13
+
14
+
15
+ def byteset(a: bytearray, i: int) -> bytes:
16
+ """Set the i'th bit in the bytearray a to 1."""
17
+ i1, i0 = divmod(i, 8)
18
+ a[i1] |= 2**i0
19
+
20
+
21
+ def bytetest(a: bytes, i: int) -> bytes:
22
+ """Test if the i'th bit in the bytestring a is 1."""
23
+ i1, i0 = divmod(i, 8)
24
+ return (a[i1] & 2**i0) != 0
25
+
26
+
27
+ def factor_qs(n: int) -> list:
28
+ """Find factors of n using the quadratic sieve."""
29
+ # first determine the bound B for the factorbase: Choosing B=p^(1/u) Canfield-Erdös-Pomerance gives us
30
+ # the expected running time |B|^2 u^u = u^(u+2) p^(2/u)/log(n). There is no explicit expression for the optimum, hence
31
+ # we use Newton
32
+ u = 2 * sqrt(log(n) / log(log(n))) # asymptotic value
33
+ for _ in range(3):
34
+ u = (2 * log(n) + u * u * (2 + log(u))) / (
35
+ 2 + 3 * u + 2 * u * log(u)
36
+ ) # Newton iteration
37
+ B = int(exp(log(n) / u))
38
+ # B = int(exp(0.5 * sqrt( log(n) * log(log(n)) )*( 1 + 1/log(log(n)) )))
39
+
40
+ factorbase = []
41
+ for p in sieve_eratosthenes(B): # compute the factorbase
42
+ ls = legendre_symbol(n, p)
43
+ if ls == 0: # we already found a factor;-)
44
+ if n == p:
45
+ return n
46
+ return p
47
+ if ls == 1: # we only take primes such that n is quadratic residue
48
+ factorbase.append(p)
49
+ lf = len(factorbase) # length of the factorbase (including -1)
50
+ lfb = ceil((lf+1) / 8) # the number of bytes we need to store a relation
51
+
52
+ factorbase_log = [log(p) for p in factorbase] # we add these up to test if a number will probably factor
53
+ factorbase_root = [0] * lf # compute the roots of n mod p
54
+ for i, p in enumerate(factorbase):
55
+ r1 = sqrt_mod(n, p)
56
+ assert r1 is not None # only quadratic residues
57
+ r2 = -r1 % p
58
+ if r1 == r2:
59
+ factorbase_root[i] = [ r1 ]
60
+ else:
61
+ factorbase_root[i] = [ r1, -r1 % p ]
62
+
63
+ m = isqrt(n - 1) + 1
64
+ d = m**2 - n
65
+ if d == 0: # Our number is a square
66
+ return m
67
+ m2 = 2 * m
68
+
69
+ relation_no = -1
70
+ relations = [None] * (lf + 1) # here we will store the relations in case we found a B-smooth number
71
+ values = [None] * (lf + 1) # here we will store the values leading to the B-smooth numbers
72
+
73
+ # set up the sieve
74
+ sieve_step = 100
75
+ sieve_bound = sieve_step
76
+ # start values for the iterators in the positive/negative direction
77
+ iterator_p = [ [0, 0] for _ in range(lf) ]
78
+ iterator_m = [ [0, 0] for _ in range(lf) ]
79
+ for i, p in enumerate(factorbase):
80
+ for j, r in enumerate(factorbase_root[i]):
81
+ iterator_p[i][j] = (r - m) % p
82
+ iterator_m[i][j] = iterator_p[i][j] - p # one step in the negative direction
83
+
84
+ def do_sieve():
85
+ nonlocal factorbase, factorbase_root, sieve_bound, factors
86
+ for i, p in enumerate(factorbase):
87
+ for r in range(len(factorbase_root[i])):
88
+ j = iterator_p[i][r]
89
+ while j <= sieve_bound:
90
+ if j in factors:
91
+ factors[j][0].append(i)
92
+ factors[j][1] += factorbase_log[i]
93
+ else:
94
+ factors[j] = [[i], factorbase_log[i]]
95
+ j += p
96
+ iterator_p[i][r] = j # store as start value for the next step
97
+ j = iterator_m[i][r]
98
+ while j > -sieve_bound:
99
+ if j in factors:
100
+ factors[j][0].append(i)
101
+ factors[j][1] += factorbase_log[i]
102
+ else:
103
+ factors[j] = [[i], factorbase_log[i]]
104
+ j -= p
105
+ iterator_m[i][r] = j # store as start value for the next step
106
+
107
+ def process_relation(j: int, relation: bytes):
108
+ nonlocal relation_no, values, relations
109
+ relation_no += 1
110
+ rhs = bytearray(b"\x00") * lfb # construct the k'th row of the identity matrix
111
+ byteset(rhs, relation_no)
112
+ relation += rhs # extend the relation with this row
113
+ values[relation_no] = j # store the value which lead to the relation
114
+ # do the Gauss elimination
115
+ index = lf # this will be the index of the first nonzero entry
116
+ # print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
117
+ for i in range(lf):
118
+ if bytetest(relation, i) and relations[i] is not None: # make this entry zero if we can (Gauss elimination)
119
+ bytexor(relation, relations[i])
120
+ if bytetest(relation, i) and index == lf: # is this the index of the first nonzero entry?
121
+ index = i
122
+ # print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation)))
123
+ if index == lf: # the new relation is linearly dependent: we have found a linear combination of the 0 vector
124
+ # now we need to determine the factors
125
+ u = 1 # product over all values m - j such that f(m-j) is B-smooth
126
+ v = 1 # sqrt of the product over all f(m-j) which are B-smooth
127
+ w = 1 # save nonsquare terms for the next round
128
+ for i in range(lf):
129
+ if bytetest(relation, lfb * 8 + i): # select the relations which sum to the 0 vector
130
+ ui = m + values[i]
131
+ u = (u * ui) % n
132
+ vi = ui ** 2 - n
133
+ d = gcd(w, vi)
134
+ v = (v * d) % n # sqrt of the part which is already square
135
+ w = (w // d) * (vi // d) # this part is not square yet
136
+ v = v * isqrt(w) % n
137
+ res = gcd(u - v, n)
138
+ if 1 < res < n:
139
+ return res
140
+ relation_no -= 1 # this one did not work, try again
141
+ else:
142
+ relations[index] = relation
143
+ return None
144
+
145
+ while True:
146
+ factors = {} # store the index of the primes dividing j
147
+
148
+ do_sieve()
149
+
150
+ for j in sorted(factors.keys(),key=abs):
151
+ v = d + (m2 + j) * j # (j + m) ** 2 - n
152
+ if factors[j][1] > 0.49 * log(abs(v)):
153
+ primes = factors[j][0]
154
+ mask = bytearray(b"\x00") * lfb
155
+ if v < 0:
156
+ byteset(mask, 0)
157
+ v *= -1
158
+ for i in primes:
159
+ p = factorbase[i]
160
+ k = 1 # per our sieve we already know that p devides v
161
+ v //= p
162
+ while v % p == 0: # divide by p as many times as possible
163
+ k = (k + 1) % 2 # we only want to know if the exponent is even or odd
164
+ v //= p
165
+ if k:
166
+ byteset(mask, i + 1)
167
+ if v == 1:
168
+ res = process_relation(j, mask)
169
+ if res:
170
+ return res
171
+ del(factors[j])
172
+ else:
173
+ del(factors[j]) # the number is unlikely to factor
174
+ sieve_bound += sieve_step # increase the sieve and continue
kryptools/la.py ADDED
@@ -0,0 +1,223 @@
1
+ """
2
+ Linear algebra
3
+ """
4
+
5
+ from math import sqrt, prod
6
+
7
+ class Matrix:
8
+ """
9
+ Matrix class.
10
+
11
+ Example:
12
+
13
+ To define a matrix use
14
+ >>> Matrix([[1, 2], [3, 4]])
15
+ [1, 2]
16
+ [3, 4]
17
+ """
18
+ def __init__(self, matrix, ring = None):
19
+ if not isinstance(matrix[0], list|tuple):
20
+ matrix = [ [x] for x in matrix ]
21
+ self.matrix = matrix
22
+ self.cols = len(matrix[0])
23
+ self.rows = len(matrix)
24
+ if ring:
25
+ self.map(ring)
26
+
27
+ def __repr__(self) -> str:
28
+ out = ["[ "] * self.rows
29
+ for j in range(self.cols):
30
+ tmp = [ "" ] * self.rows
31
+ max_len = 0
32
+ for i in range(self.rows):
33
+ tmp[i] = str(self.matrix[i][j])
34
+ max_len = max(max_len, len(tmp[i]))
35
+ for i in range(self.rows):
36
+ out[i] += " " * (max_len - len(tmp[i])) + tmp[i]
37
+ if j < self.cols - 1:
38
+ out[i] += ", "
39
+ for i in range(self.rows):
40
+ out[i] += " ]"
41
+ return '\n'.join(out)
42
+
43
+ def __len__(self):
44
+ return self.cols * self.rows
45
+
46
+ def __getitem__(self, item):
47
+ if isinstance(item, tuple):
48
+ i, j = item
49
+ if isinstance(i, int) and isinstance(j, int):
50
+ return self.matrix[i][j]
51
+ rows = range(self.rows)[i]
52
+ if isinstance(i, int):
53
+ rows = [ rows ]
54
+ cols = range(self.cols)[j]
55
+ if isinstance(j, int):
56
+ cols = [ cols ]
57
+ return Matrix([[self.matrix[i][j] for j in cols] for i in rows])
58
+ i, j = divmod(item, self.cols)
59
+ return self.matrix[i][j]
60
+
61
+ def __setitem__(self, item, value):
62
+ if isinstance(item, tuple):
63
+ i, j = item
64
+ if isinstance(i, int) and isinstance(j, int):
65
+ self.matrix[i][j] = value
66
+ return
67
+ rows = range(self.rows)[i]
68
+ if isinstance(i, int):
69
+ rows = [ rows ]
70
+ cols = range(self.cols)[j]
71
+ if isinstance(j, int):
72
+ cols = [ cols ]
73
+ for i, ii in zip(cols,range(len(cols))):
74
+ for j, jj in zip(rows,range(len(rows))):
75
+ self.matrix[j][i] = value[jj,ii]
76
+ return
77
+ i, j = divmod(item, self.cols)
78
+ self.matrix[i][j] = value
79
+
80
+ def __eq__(self, other):
81
+ if not isinstance(other, self.__class__):
82
+ return False
83
+ return self.matrix == other.matrix
84
+
85
+ def map(self, func):
86
+ "Apply a function to all elements in place"
87
+ for row in self.matrix:
88
+ row[:] = map(func, row)
89
+
90
+ def applyfunc(self, func):
91
+ "Apply a function to all elements"
92
+ tmp = self[:,:]
93
+ tmp.map(func)
94
+ return tmp
95
+
96
+ def norm2(self) -> float:
97
+ "Squared Frobenius/Euclidean norm."
98
+ return sum( sum(x*x for x in row) for row in self.matrix )
99
+
100
+ def norm(self) -> float:
101
+ "Frobenius/Euclidean norm."
102
+ return sqrt(self.norm2())
103
+
104
+ def dot(self, other) -> int:
105
+ if self.rows == 1 and other.rows == 1 and self.cols == other.cols:
106
+ return sum(x * y for x, y in zip(self.matrix[0], other.matrix[0]))
107
+ if self.cols == 1 and other.cols == 1 and self.rows == other.rows:
108
+ return sum(x[0] * y[0] for x, y in zip(self.matrix, other.matrix))
109
+ return NotImplemented
110
+
111
+ def transpose(self) -> "Matrix":
112
+ return Matrix([list(i) for i in zip(*self.matrix)])
113
+
114
+ def multiply(self, other) -> "Matrix":
115
+ if not isinstance(other, Matrix) or self.cols != other.rows:
116
+ return NotImplemented
117
+ result = [[0 for j in range(other.cols)] for i in range(self.rows)]
118
+ for i in range(self.rows):
119
+ for j in range(other.cols):
120
+ for k in range(other.rows):
121
+ result[i][j] += self.matrix[i][k] * other.matrix[k][j]
122
+ return Matrix(result)
123
+
124
+ def __add__(self, other) -> "Matrix":
125
+ if isinstance(other, Matrix) and other.cols == self.cols and other.rows == self.rows:
126
+ return Matrix([ [ x1 + y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
127
+ return NotImplemented
128
+
129
+ def __sub__(self, other) -> "Matrix":
130
+ if isinstance(other, Matrix) and other.cols == self.cols and other.rows == self.rows:
131
+ return Matrix([ [ x1 - y1 for x1, y1 in zip(x,y)] for x, y in zip(self.matrix, other.matrix)])
132
+ return NotImplemented
133
+
134
+ def __neg__(self) -> "Matrix":
135
+ return -1 * self
136
+
137
+ def __mul__(self, other) -> "Matrix":
138
+ if isinstance(other, Matrix):
139
+ return self.multiply(other)
140
+ return NotImplemented
141
+
142
+ def __rmul__(self, other) -> "Matrix":
143
+ if isinstance(other, Matrix):
144
+ return self.multiply(other)
145
+ return Matrix([ [item * other for item in row] for row in self.matrix ])
146
+
147
+ def rref(self) -> "Matrix":
148
+ "Compute the reduced echelon form of a matrix M."
149
+ n, m = self.cols, self.rows
150
+ R = self[:, :]
151
+ i = 0
152
+ for j in range(n):
153
+ if not R[i, j]: # search for am nonzero entry in the present column
154
+ for ii in range(i+1,m):
155
+ if R[ii, j]:
156
+ R[i, :], R[ii, :] = R[ii, :], R[i, :] # swap rows
157
+ break
158
+ else:
159
+ continue # all entries are zero
160
+ if R[i, j] != 1:
161
+ R[i, :] = 1/ R[i, j] * R[i, :] # make the pivot one
162
+ for ii in range(m): # remove the column entries above/below the pivot
163
+ if i == ii:
164
+ continue
165
+ tmp = R[ii, j]
166
+ R[ii, ::] -= tmp * R[i, :]
167
+ i += 1
168
+ if i == m:
169
+ break
170
+ return R
171
+
172
+ def det(self) -> int:
173
+ "Compute the determinant of a matrix M."
174
+ if self.rows != self.rows:
175
+ raise ValueError("Matrix must be square!")
176
+ n = self.cols
177
+ R = self[:, :]
178
+ D = 1
179
+ i = 0
180
+ for j in range(n):
181
+ if not R[i, j]: # search for am nonzero entry in the present column
182
+ for ii in range(i+1,m):
183
+ if R[ii, j]:
184
+ D *= -1
185
+ R[i, :], R[ii, :] = R[ii, :], R[i, :] # swap rows
186
+ break
187
+ else:
188
+ return 0 # all entries are zero
189
+ if R[i, j] != 1:
190
+ D *= R[i, j]
191
+ R[i, :] = 1/ R[i, j] * R[i, :] # make the pivot one
192
+ for ii in range(i+1,n): # remove the column entries below the pivot
193
+ if i == ii:
194
+ continue
195
+ tmp = R[ii, j]
196
+ R[ii, ::] -= tmp * R[i, :]
197
+ i += 1
198
+ return D
199
+
200
+ def inv(self) -> "Matrix":
201
+ "Compute the inverse of a square matrix M."
202
+ if self.rows != self.rows:
203
+ raise ValueError("Matrix must be square!")
204
+ n = self.cols
205
+ MM = Matrix([[0 for _ in range(2*n)] for _ in range(n)])
206
+ for i in range(n):
207
+ MM[i,n+i] = 1
208
+ MM[:,0:n] = self
209
+ MM = MM.rref()
210
+ if not prod(MM[i, i] for i in range(n)):
211
+ raise ValueError("Matrix is not invertible!")
212
+ return MM[:,n:]
213
+
214
+
215
+ def zeros(m:int, n: int) -> "Matrix":
216
+ return Matrix([[ 0 for j in range(n)] for i in range(m) ])
217
+
218
+ def eye(m:int, n: int) -> "Matrix":
219
+ def delta(i, j):
220
+ if i == j:
221
+ return 1
222
+ return 0
223
+ return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ])