pqlattice 0.1.2__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.
- pqlattice/__init__.py +6 -0
- pqlattice/_backends/__init__.py +0 -0
- pqlattice/_backends/_fast.py +75 -0
- pqlattice/_backends/_native.py +33 -0
- pqlattice/_backends/_protocol.py +15 -0
- pqlattice/_utils.py +201 -0
- pqlattice/integer/__init__.py +8 -0
- pqlattice/integer/_integer.py +55 -0
- pqlattice/integer/_modintring.py +246 -0
- pqlattice/integer/_modring.py +165 -0
- pqlattice/integer/_primality.py +78 -0
- pqlattice/integer/_primes.py +57 -0
- pqlattice/lattice/__init__.py +44 -0
- pqlattice/lattice/_bkz.py +87 -0
- pqlattice/lattice/_cvp.py +62 -0
- pqlattice/lattice/_embeddings.py +149 -0
- pqlattice/lattice/_gso.py +43 -0
- pqlattice/lattice/_hkz.py +20 -0
- pqlattice/lattice/_lattice.py +137 -0
- pqlattice/lattice/_lll.py +93 -0
- pqlattice/lattice/_svp.py +89 -0
- pqlattice/lattice/embeddings.py +3 -0
- pqlattice/linalg/__init__.py +37 -0
- pqlattice/linalg/_linalg.py +306 -0
- pqlattice/linalg/_modint.py +209 -0
- pqlattice/linalg/_utils.py +167 -0
- pqlattice/polynomial/__init__.py +5 -0
- pqlattice/polynomial/_modpolyqring.py +185 -0
- pqlattice/polynomial/_modpolyring.py +267 -0
- pqlattice/polynomial/_poly.py +250 -0
- pqlattice/polynomial/poly.py +3 -0
- pqlattice/py.typed +0 -0
- pqlattice/random/__init__.py +7 -0
- pqlattice/random/_distribution.py +303 -0
- pqlattice/random/_lattice.py +53 -0
- pqlattice/random/_lwe.py +109 -0
- pqlattice/random/_lwr.py +41 -0
- pqlattice/random/_prime.py +53 -0
- pqlattice/random/distribution.py +3 -0
- pqlattice/settings.py +66 -0
- pqlattice/typing/__init__.py +4 -0
- pqlattice/typing/_types.py +18 -0
- pqlattice/typing/_types_validator.py +57 -0
- pqlattice-0.1.2.dist-info/METADATA +33 -0
- pqlattice-0.1.2.dist-info/RECORD +47 -0
- pqlattice-0.1.2.dist-info/WHEEL +4 -0
- pqlattice-0.1.2.dist-info/licenses/LICENSE +7 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import random
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, override
|
|
5
|
+
|
|
6
|
+
from .._utils import as_integer
|
|
7
|
+
from ..typing import Matrix, Vector
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Distribution(ABC):
|
|
11
|
+
def __init__(self, seed: int | None = None):
|
|
12
|
+
self._pyrng = random.Random(seed)
|
|
13
|
+
|
|
14
|
+
def set_seed(self, seed: int | None) -> None:
|
|
15
|
+
if seed is not None:
|
|
16
|
+
self._pyrng.seed(seed)
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def sample_int(self, seed: int | None) -> int: ...
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def sample_vector(self, n: int, seed: int | None = None) -> Vector: ...
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def sample_matrix(self, rows: int, cols: int | None = None, seed: int | None = None) -> Matrix: ...
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def set_params(self, *args: Any) -> None: ...
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def get_params(self) -> dict[str, Any]: ...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Uniform(Distribution):
|
|
35
|
+
def __init__(self, range_beg: int, range_end: int, seed: int | None = None):
|
|
36
|
+
"""
|
|
37
|
+
Creates a uniform sampler from range [range_beg; range_end].
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
range_beg : int
|
|
42
|
+
begin of sampling range. Inclusive
|
|
43
|
+
range_end : int
|
|
44
|
+
end of sampling range. Inclusive
|
|
45
|
+
seed : int | None, optional
|
|
46
|
+
seed for random number generator.
|
|
47
|
+
"""
|
|
48
|
+
super().__init__(seed)
|
|
49
|
+
self._range_beg = range_beg
|
|
50
|
+
self._range_end = range_end
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
def sample_int(self, seed: int | None = None) -> int:
|
|
54
|
+
"""
|
|
55
|
+
Get uniform random int from range [self.beg_range, self.end_range]
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
seed : int | None, optional
|
|
60
|
+
set the new seed, if None does nothing
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
int
|
|
65
|
+
random integer from range [self.beg_range, self.end_range]
|
|
66
|
+
"""
|
|
67
|
+
self.set_seed(seed)
|
|
68
|
+
return self._pyrng.randint(self._range_beg, self._range_end)
|
|
69
|
+
|
|
70
|
+
@override
|
|
71
|
+
def sample_vector(self, n: int, seed: int | None = None) -> Vector:
|
|
72
|
+
"""
|
|
73
|
+
_summary_
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
n : int
|
|
78
|
+
_description_
|
|
79
|
+
seed : int | None, optional
|
|
80
|
+
_description_, by default None
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
Vector
|
|
85
|
+
_description_
|
|
86
|
+
"""
|
|
87
|
+
self.set_seed(seed)
|
|
88
|
+
return as_integer([self.sample_int() for _ in range(n)])
|
|
89
|
+
|
|
90
|
+
@override
|
|
91
|
+
def sample_matrix(self, rows: int, cols: int | None = None, seed: int | None = None) -> Matrix:
|
|
92
|
+
"""
|
|
93
|
+
_summary_
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
rows : int
|
|
98
|
+
_description_
|
|
99
|
+
cols : int | None, optional
|
|
100
|
+
_description_, by default None
|
|
101
|
+
seed : int | None, optional
|
|
102
|
+
_description_, by default None
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
Matrix
|
|
107
|
+
_description_
|
|
108
|
+
"""
|
|
109
|
+
self.set_seed(seed)
|
|
110
|
+
if cols is None:
|
|
111
|
+
cols = rows
|
|
112
|
+
return as_integer([[self.sample_int() for _ in range(cols)] for _ in range(rows)])
|
|
113
|
+
|
|
114
|
+
@override
|
|
115
|
+
def set_params(self, range_beg: int | None = None, range_end: int | None = None) -> None: # type: ignore
|
|
116
|
+
"""
|
|
117
|
+
_summary_
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
range_beg : int | None, optional
|
|
122
|
+
_description_, by default None
|
|
123
|
+
range_end : int | None, optional
|
|
124
|
+
_description_, by default None
|
|
125
|
+
"""
|
|
126
|
+
if range_beg is None:
|
|
127
|
+
range_beg = self._range_beg
|
|
128
|
+
if range_end is None:
|
|
129
|
+
range_end = self._range_end
|
|
130
|
+
|
|
131
|
+
self._range_beg = range_beg
|
|
132
|
+
self._range_end = range_end
|
|
133
|
+
|
|
134
|
+
@override
|
|
135
|
+
def get_params(self) -> dict[str, int]:
|
|
136
|
+
"""
|
|
137
|
+
_summary_
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
dict[str, int]
|
|
142
|
+
_description_
|
|
143
|
+
"""
|
|
144
|
+
return {"range_beg": self._range_beg, "range_end": self._range_end}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class DiscreteGaussian(Distribution):
|
|
148
|
+
def __init__(self, sigma: float, center: int | float = 0, tail_cut: float = 6.0, seed: int | None = None):
|
|
149
|
+
"""
|
|
150
|
+
_summary_
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
sigma : float
|
|
155
|
+
_description_
|
|
156
|
+
center : int | float, optional
|
|
157
|
+
_description_, by default 0
|
|
158
|
+
tail_cut : float, optional
|
|
159
|
+
_description_, by default 6.0
|
|
160
|
+
seed : int | None, optional
|
|
161
|
+
_description_, by default None
|
|
162
|
+
"""
|
|
163
|
+
super().__init__(seed)
|
|
164
|
+
self.center = center
|
|
165
|
+
self.sigma = sigma
|
|
166
|
+
self.tail_cut = tail_cut
|
|
167
|
+
|
|
168
|
+
self._table: dict[int, float] = {}
|
|
169
|
+
self._recompute_table()
|
|
170
|
+
|
|
171
|
+
def _recompute_table(self) -> None:
|
|
172
|
+
self.bound = int(math.ceil(self.tail_cut * self.sigma))
|
|
173
|
+
self._table: dict[int, float] = {}
|
|
174
|
+
for x in range(-self.bound, self.bound + 1):
|
|
175
|
+
self._table[x] = math.exp(-(x**2) / (2 * self.sigma**2))
|
|
176
|
+
|
|
177
|
+
@override
|
|
178
|
+
def sample_int(self, seed: int | None = None) -> int:
|
|
179
|
+
"""
|
|
180
|
+
_summary_
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
seed : int | None, optional
|
|
185
|
+
_description_, by default None
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
int
|
|
190
|
+
_description_
|
|
191
|
+
"""
|
|
192
|
+
self.set_seed(seed)
|
|
193
|
+
|
|
194
|
+
if isinstance(self.center, int):
|
|
195
|
+
return self._sample_centered_fast() + self.center
|
|
196
|
+
|
|
197
|
+
return self._sample_dynamic_rejection(self.center)
|
|
198
|
+
|
|
199
|
+
@override
|
|
200
|
+
def sample_vector(self, n: int, seed: int | None = None) -> Vector:
|
|
201
|
+
"""
|
|
202
|
+
_summary_
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
n : int
|
|
207
|
+
_description_
|
|
208
|
+
seed : int | None, optional
|
|
209
|
+
_description_, by default None
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
Vector
|
|
214
|
+
_description_
|
|
215
|
+
"""
|
|
216
|
+
self.set_seed(seed)
|
|
217
|
+
return as_integer([self.sample_int() for _ in range(n)])
|
|
218
|
+
|
|
219
|
+
@override
|
|
220
|
+
def sample_matrix(self, rows: int, cols: int | None = None, seed: int | None = None) -> Matrix:
|
|
221
|
+
"""
|
|
222
|
+
_summary_
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
rows : int
|
|
227
|
+
_description_
|
|
228
|
+
cols : int | None, optional
|
|
229
|
+
_description_, by default None
|
|
230
|
+
seed : int | None, optional
|
|
231
|
+
_description_, by default None
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
Matrix
|
|
236
|
+
_description_
|
|
237
|
+
"""
|
|
238
|
+
self.set_seed(seed)
|
|
239
|
+
if cols is None:
|
|
240
|
+
cols = rows
|
|
241
|
+
return as_integer([[self.sample_int() for _ in range(cols)] for _ in range(rows)])
|
|
242
|
+
|
|
243
|
+
@override
|
|
244
|
+
def set_params(self, sigma: float | None = None, center: float | int | None = None, tail_cut: float | None = None) -> None: # type: ignore
|
|
245
|
+
"""
|
|
246
|
+
_summary_
|
|
247
|
+
|
|
248
|
+
Parameters
|
|
249
|
+
----------
|
|
250
|
+
sigma : float | None, optional
|
|
251
|
+
_description_, by default None
|
|
252
|
+
center : float | int | None, optional
|
|
253
|
+
_description_, by default None
|
|
254
|
+
tail_cut : float | None, optional
|
|
255
|
+
_description_, by default None
|
|
256
|
+
"""
|
|
257
|
+
if sigma is None:
|
|
258
|
+
sigma = self.sigma
|
|
259
|
+
if center is None:
|
|
260
|
+
center = self.center
|
|
261
|
+
if tail_cut is None:
|
|
262
|
+
tail_cut = self.tail_cut
|
|
263
|
+
|
|
264
|
+
self.sigma = sigma
|
|
265
|
+
self.tail_cut = tail_cut
|
|
266
|
+
self.center = center
|
|
267
|
+
self._recompute_table()
|
|
268
|
+
|
|
269
|
+
def get_params(self) -> dict[str, float]:
|
|
270
|
+
"""
|
|
271
|
+
_summary_
|
|
272
|
+
|
|
273
|
+
Returns
|
|
274
|
+
-------
|
|
275
|
+
dict[str, float]
|
|
276
|
+
_description_
|
|
277
|
+
"""
|
|
278
|
+
return {"sigma": self.sigma, "center": self.center, "tail_cut": self.tail_cut, "bound": self.bound}
|
|
279
|
+
|
|
280
|
+
def _sample_centered_fast(self) -> int:
|
|
281
|
+
max_iters = 1000
|
|
282
|
+
for _ in range(max_iters):
|
|
283
|
+
x = random.randint(-self.bound, self.bound)
|
|
284
|
+
prob = self._table.get(x, 0.0)
|
|
285
|
+
if random.random() < prob:
|
|
286
|
+
return x
|
|
287
|
+
|
|
288
|
+
raise RuntimeError("Failed to generate sample")
|
|
289
|
+
|
|
290
|
+
def _sample_dynamic_rejection(self, c: float) -> int:
|
|
291
|
+
start = int(math.floor(c - self.bound))
|
|
292
|
+
end = int(math.ceil(c + self.bound))
|
|
293
|
+
|
|
294
|
+
max_iters = 1000
|
|
295
|
+
for _ in range(max_iters):
|
|
296
|
+
x = random.randint(start, end)
|
|
297
|
+
dist_sq = (x - c) ** 2
|
|
298
|
+
prob = math.exp(-dist_sq / (2 * self.sigma**2))
|
|
299
|
+
|
|
300
|
+
if random.random() < prob:
|
|
301
|
+
return x
|
|
302
|
+
|
|
303
|
+
raise RuntimeError("Failed to generate sample")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from .._utils import as_integer, zeros_mat
|
|
6
|
+
from ..typing import SquareMatrix
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _gen_unimodular(n: int, rounds: int, rng: random.Random) -> SquareMatrix:
|
|
10
|
+
U = as_integer(np.eye(n))
|
|
11
|
+
for _ in range(rounds):
|
|
12
|
+
i, j = rng.sample(range(n), 2)
|
|
13
|
+
coeff = rng.sample([-1, 1], 1)
|
|
14
|
+
|
|
15
|
+
U[i] += coeff * U[j]
|
|
16
|
+
|
|
17
|
+
return U
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def randlattice(n: int, det_upper_bound: int | None = None, seed: int | None = None) -> SquareMatrix:
|
|
21
|
+
"""
|
|
22
|
+
Generates lattice basis by, first generating random square matrix in Hermite normal form and then by transforming it using random unimodular matrix.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
n : int
|
|
27
|
+
lattice's rank
|
|
28
|
+
det_upper_bound : int | None, optional
|
|
29
|
+
upper bound of lattice volume, by default 2 ** n
|
|
30
|
+
seed : int | None, optional
|
|
31
|
+
seed for random number generator
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
SquareMatrix
|
|
36
|
+
n x n matrix representing lattice basis
|
|
37
|
+
"""
|
|
38
|
+
det_ub: int = 2**n if det_upper_bound is None else det_upper_bound
|
|
39
|
+
|
|
40
|
+
rng = random.Random(seed)
|
|
41
|
+
diagonals = [rng.randint(1, det_ub) for _ in range(n)]
|
|
42
|
+
|
|
43
|
+
H = zeros_mat(n)
|
|
44
|
+
for i in range(n):
|
|
45
|
+
H[i, i] = diagonals[i]
|
|
46
|
+
modulus = H[i, i]
|
|
47
|
+
for j in range(i + 1, n):
|
|
48
|
+
H[i, j] = rng.randint(0, modulus - 1)
|
|
49
|
+
|
|
50
|
+
U = _gen_unimodular(n, n * 5, rng)
|
|
51
|
+
|
|
52
|
+
basis = U @ H
|
|
53
|
+
return basis
|
pqlattice/random/_lwe.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..integer._modring import cmodl, mod
|
|
4
|
+
from ..typing import Matrix, Vector
|
|
5
|
+
from ._distribution import DiscreteGaussian, Uniform
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LWE:
|
|
9
|
+
def __init__(self, n: int, q: int, sigma: float, secret_distribution: str, seed: int):
|
|
10
|
+
"""
|
|
11
|
+
Creates LWE sampler with DiscreteGuassianDistribution centered at 0 as noise sampler
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
n : int
|
|
16
|
+
length of secret vector
|
|
17
|
+
q : int
|
|
18
|
+
modulus
|
|
19
|
+
sigma : float
|
|
20
|
+
sigma value for DiscreteGaussianDistribution
|
|
21
|
+
seed : int
|
|
22
|
+
seed for random number generator
|
|
23
|
+
"""
|
|
24
|
+
self.n = n
|
|
25
|
+
self.q = q
|
|
26
|
+
self.U = Uniform(0, q - 1, seed=seed)
|
|
27
|
+
self.D = DiscreteGaussian(sigma, seed=seed)
|
|
28
|
+
|
|
29
|
+
secret = self.U.sample_vector(n)
|
|
30
|
+
if secret_distribution == "uniform":
|
|
31
|
+
self._secret = secret
|
|
32
|
+
elif secret_distribution == "binary":
|
|
33
|
+
self._secret = mod(secret, 2)
|
|
34
|
+
elif secret_distribution == "ternary":
|
|
35
|
+
self._secret = cmodl(secret, 3)
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError(f"Unknown distribution {secret_distribution}, expected uniform|binary|ternary")
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def secret(self) -> Vector:
|
|
41
|
+
"""
|
|
42
|
+
Retrieve underlying secret
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
Vector
|
|
47
|
+
s: n-vector
|
|
48
|
+
"""
|
|
49
|
+
return self._secret
|
|
50
|
+
|
|
51
|
+
def set_secret(self, secret: Vector) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Set the underlying secret
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
secret : Vector
|
|
58
|
+
secret vector to set
|
|
59
|
+
|
|
60
|
+
Raises
|
|
61
|
+
------
|
|
62
|
+
ValueError
|
|
63
|
+
when lenght of the provided vector is not correct with the parameter of the LWE sampler
|
|
64
|
+
"""
|
|
65
|
+
if secret.shape[0] != self.n:
|
|
66
|
+
raise ValueError(f"expected {self.n} dimension of secret, got {secret.shape[0]}")
|
|
67
|
+
|
|
68
|
+
self._secret = secret
|
|
69
|
+
|
|
70
|
+
def sample_matrix(self, m: int) -> tuple[Matrix, Vector]:
|
|
71
|
+
"""
|
|
72
|
+
Generates a full matrix system (A, b) with 'm' samples.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
m : int
|
|
77
|
+
how many samples should the resulting matrix have
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
tuple[Matrix, Vector]:
|
|
82
|
+
|
|
83
|
+
A (Matrix): m x n matrix (Uniform mod q)
|
|
84
|
+
b (Vector): m-vector (As + e mod q)
|
|
85
|
+
"""
|
|
86
|
+
A = self.U.sample_matrix(m, self.n)
|
|
87
|
+
e = self.D.sample_vector(m)
|
|
88
|
+
|
|
89
|
+
b = mod(A @ self.secret + e, self.q)
|
|
90
|
+
|
|
91
|
+
return A, b
|
|
92
|
+
|
|
93
|
+
def next_sample(self) -> tuple[Vector, int]:
|
|
94
|
+
"""
|
|
95
|
+
Generates a single sample pair (a, b).
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
tuple[Vector, int]
|
|
100
|
+
a (Vector): n-vector (Uniform mod q)
|
|
101
|
+
b (int): as + e mod q
|
|
102
|
+
"""
|
|
103
|
+
a = self.U.sample_vector(self.n)
|
|
104
|
+
e = self.D.sample_int()
|
|
105
|
+
|
|
106
|
+
_as: int = np.dot(a, self.secret)
|
|
107
|
+
b = mod(_as + e, self.q)
|
|
108
|
+
|
|
109
|
+
return a, b
|
pqlattice/random/_lwr.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import overload
|
|
2
|
+
|
|
3
|
+
from ..typing import Matrix, Vector
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LWR:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
"""_summary_
|
|
9
|
+
|
|
10
|
+
Raises
|
|
11
|
+
------
|
|
12
|
+
NotImplementedError
|
|
13
|
+
_description_
|
|
14
|
+
"""
|
|
15
|
+
raise NotImplementedError()
|
|
16
|
+
|
|
17
|
+
@overload
|
|
18
|
+
def __call__(self, n: int) -> Matrix: ...
|
|
19
|
+
|
|
20
|
+
@overload
|
|
21
|
+
def __call__(self, n: None) -> Vector: ...
|
|
22
|
+
|
|
23
|
+
def __call__(self, n: int | None = None) -> Matrix | Vector:
|
|
24
|
+
"""_summary_
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
n : int | None, optional
|
|
29
|
+
_description_, by default None
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
Matrix | Vector
|
|
34
|
+
_description_
|
|
35
|
+
|
|
36
|
+
Raises
|
|
37
|
+
------
|
|
38
|
+
NotImplementedError
|
|
39
|
+
_description_
|
|
40
|
+
"""
|
|
41
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
from ..integer._primality import is_prime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _randprime(a: int, b: int, seed: int | None = None) -> int:
|
|
8
|
+
def ilog(n: int, base: float = math.e) -> int:
|
|
9
|
+
return int((n.bit_length() - 1) / math.log2(base))
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
approx_number_of_primes_to_a = 0 if a == 0 else a // ilog(a)
|
|
13
|
+
approx_number_of_primes_to_b = 0 if b == 0 else b // ilog(b)
|
|
14
|
+
approx_number_of_primes = approx_number_of_primes_to_b - approx_number_of_primes_to_a
|
|
15
|
+
prime_proba = approx_number_of_primes / (b - a)
|
|
16
|
+
number_of_samples = int(math.log(0.001) / math.log(1 - prime_proba)) + 1
|
|
17
|
+
except ZeroDivisionError:
|
|
18
|
+
number_of_samples = b - a
|
|
19
|
+
|
|
20
|
+
if b - a < 1000:
|
|
21
|
+
number_of_samples = b - a
|
|
22
|
+
|
|
23
|
+
random.seed(seed)
|
|
24
|
+
for i in range(number_of_samples):
|
|
25
|
+
prime_candidate = random.randint(a, b)
|
|
26
|
+
if is_prime(prime_candidate):
|
|
27
|
+
return prime_candidate
|
|
28
|
+
if is_prime(a + i):
|
|
29
|
+
return a + i
|
|
30
|
+
|
|
31
|
+
raise ValueError(f"Couldn't find a prime number in interval [{a}, {b})")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def randprime(kbits: int, seed: int | None = None) -> int:
|
|
35
|
+
"""
|
|
36
|
+
Generates random prime number from range [2 ** (kbits - 1); 2 ** (kbist)].
|
|
37
|
+
Uses Miller-Rabin primality test.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
kbits : int
|
|
42
|
+
number of bits the prime number should have
|
|
43
|
+
seed : int | None, optional
|
|
44
|
+
seed for random number generator, by default None
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
int
|
|
49
|
+
prime number
|
|
50
|
+
"""
|
|
51
|
+
a = 2 ** (kbits - 1)
|
|
52
|
+
b = 2**kbits
|
|
53
|
+
return _randprime(a, b, seed=seed)
|
pqlattice/settings.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
from collections.abc import Generator
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from ._backends._fast import FastBackend
|
|
6
|
+
from ._backends._native import NativeBackend
|
|
7
|
+
from ._backends._protocol import BackendInterface
|
|
8
|
+
|
|
9
|
+
BackendName = Literal["native", "fast"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _Settings:
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
self._active_backend_name: BackendName = "native"
|
|
15
|
+
self._native_backend = NativeBackend()
|
|
16
|
+
self._fast_backend: FastBackend | None = None
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def backend_name(self) -> BackendName:
|
|
20
|
+
return self._active_backend_name
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def backend(self) -> BackendInterface:
|
|
24
|
+
if self.backend_name == "native":
|
|
25
|
+
return self._native_backend
|
|
26
|
+
elif self.backend_name == "fast":
|
|
27
|
+
if self._fast_backend is None:
|
|
28
|
+
self._fast_backend = FastBackend()
|
|
29
|
+
return self._fast_backend
|
|
30
|
+
|
|
31
|
+
raise RuntimeError(f"No backend {self.backend_name}")
|
|
32
|
+
|
|
33
|
+
def set_backend(self, name: BackendName) -> None:
|
|
34
|
+
self._active_backend_name = name
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_config = _Settings()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_backend_name() -> BackendName:
|
|
41
|
+
return _config.backend_name
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_backend() -> BackendInterface:
|
|
45
|
+
return _config.backend
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def set_backend(name: BackendName) -> None:
|
|
49
|
+
_config.set_backend(name)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@contextlib.contextmanager
|
|
53
|
+
def backend(name: BackendName) -> Generator[None, None, None]:
|
|
54
|
+
"""
|
|
55
|
+
Context manager to temporarily switch the backend.
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
with pq.settings.backend("fpylll"):
|
|
59
|
+
pq.lattice.lll(B)
|
|
60
|
+
"""
|
|
61
|
+
previous = _config.backend_name
|
|
62
|
+
try:
|
|
63
|
+
set_backend(name)
|
|
64
|
+
yield
|
|
65
|
+
finally:
|
|
66
|
+
set_backend(previous)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from ._types import Array, Matrix, SquareMatrix, Vector, is_integer, is_rational
|
|
2
|
+
from ._types_validator import is_Matrix, is_SquareMatrix, is_Vector, validate_aliases
|
|
3
|
+
|
|
4
|
+
__all__ = ["Array", "Matrix", "SquareMatrix", "Vector", "is_integer", "is_rational", "validate_aliases", "is_Vector", "is_Matrix", "is_SquareMatrix"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from fractions import Fraction
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from numpy.typing import NDArray
|
|
5
|
+
|
|
6
|
+
type Array = NDArray[Any]
|
|
7
|
+
|
|
8
|
+
type Vector = NDArray[Any]
|
|
9
|
+
type Matrix = NDArray[Any]
|
|
10
|
+
type SquareMatrix = NDArray[Any]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_rational(a: Array) -> bool:
|
|
14
|
+
return isinstance(a.flat[0], Fraction)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_integer(a: Array) -> bool:
|
|
18
|
+
return isinstance(a.flat[0], int)
|