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.
Files changed (47) hide show
  1. pqlattice/__init__.py +6 -0
  2. pqlattice/_backends/__init__.py +0 -0
  3. pqlattice/_backends/_fast.py +75 -0
  4. pqlattice/_backends/_native.py +33 -0
  5. pqlattice/_backends/_protocol.py +15 -0
  6. pqlattice/_utils.py +201 -0
  7. pqlattice/integer/__init__.py +8 -0
  8. pqlattice/integer/_integer.py +55 -0
  9. pqlattice/integer/_modintring.py +246 -0
  10. pqlattice/integer/_modring.py +165 -0
  11. pqlattice/integer/_primality.py +78 -0
  12. pqlattice/integer/_primes.py +57 -0
  13. pqlattice/lattice/__init__.py +44 -0
  14. pqlattice/lattice/_bkz.py +87 -0
  15. pqlattice/lattice/_cvp.py +62 -0
  16. pqlattice/lattice/_embeddings.py +149 -0
  17. pqlattice/lattice/_gso.py +43 -0
  18. pqlattice/lattice/_hkz.py +20 -0
  19. pqlattice/lattice/_lattice.py +137 -0
  20. pqlattice/lattice/_lll.py +93 -0
  21. pqlattice/lattice/_svp.py +89 -0
  22. pqlattice/lattice/embeddings.py +3 -0
  23. pqlattice/linalg/__init__.py +37 -0
  24. pqlattice/linalg/_linalg.py +306 -0
  25. pqlattice/linalg/_modint.py +209 -0
  26. pqlattice/linalg/_utils.py +167 -0
  27. pqlattice/polynomial/__init__.py +5 -0
  28. pqlattice/polynomial/_modpolyqring.py +185 -0
  29. pqlattice/polynomial/_modpolyring.py +267 -0
  30. pqlattice/polynomial/_poly.py +250 -0
  31. pqlattice/polynomial/poly.py +3 -0
  32. pqlattice/py.typed +0 -0
  33. pqlattice/random/__init__.py +7 -0
  34. pqlattice/random/_distribution.py +303 -0
  35. pqlattice/random/_lattice.py +53 -0
  36. pqlattice/random/_lwe.py +109 -0
  37. pqlattice/random/_lwr.py +41 -0
  38. pqlattice/random/_prime.py +53 -0
  39. pqlattice/random/distribution.py +3 -0
  40. pqlattice/settings.py +66 -0
  41. pqlattice/typing/__init__.py +4 -0
  42. pqlattice/typing/_types.py +18 -0
  43. pqlattice/typing/_types_validator.py +57 -0
  44. pqlattice-0.1.2.dist-info/METADATA +33 -0
  45. pqlattice-0.1.2.dist-info/RECORD +47 -0
  46. pqlattice-0.1.2.dist-info/WHEEL +4 -0
  47. pqlattice-0.1.2.dist-info/licenses/LICENSE +7 -0
pqlattice/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from fractions import Fraction
2
+
3
+ from . import integer, lattice, linalg, polynomial, random, settings, typing
4
+ from ._utils import as_integer, as_rational, show
5
+
6
+ __all__ = ["settings", "integer", "lattice", "linalg", "polynomial", "random", "typing", "as_integer", "as_rational", "Fraction", "show"]
File without changes
@@ -0,0 +1,75 @@
1
+ from typing import Any, override
2
+
3
+ from .._utils import as_integer
4
+ from ..typing import Array, Matrix, SquareMatrix, Vector
5
+ from ._protocol import BackendInterface
6
+
7
+ try:
8
+ import fpylll
9
+
10
+ HAS_FPYLLL = True
11
+ except ImportError:
12
+ HAS_FPYLLL = False
13
+
14
+ try:
15
+ import flint
16
+
17
+ HAS_FLINT = True
18
+ except ImportError:
19
+ HAS_FLINT = False
20
+
21
+
22
+ class FastBackend(BackendInterface):
23
+ def __init__(self) -> None:
24
+ if not (HAS_FPYLLL and HAS_FLINT):
25
+ raise RuntimeError("Fast backend is unavailable - dependencies missing")
26
+
27
+ def _to_fpylll(self, a: Array) -> Any:
28
+ return fpylll.IntegerMatrix.from_matrix(a.tolist())
29
+
30
+ def _from_fpylll(self, mat: Any) -> Array:
31
+ data = [list(row) for row in mat]
32
+ return as_integer(data)
33
+
34
+ def _to_flint(self, a: Array) -> Any:
35
+ return flint.fmpz_mat(a.tolist())
36
+
37
+ def _from_flint(self, mat: Any) -> Array:
38
+ return as_integer(mat.table())
39
+
40
+ @override
41
+ def lll(self, lattice_basis: SquareMatrix, delta: float) -> SquareMatrix:
42
+ mat = self._to_fpylll(lattice_basis)
43
+ fpylll.LLL.reduction(mat, delta=delta)
44
+ return self._from_fpylll(mat)
45
+
46
+ @override
47
+ def bkz(self, lattice_basis: SquareMatrix, block_size: int, delta: float) -> SquareMatrix:
48
+ mat = self._to_fpylll(lattice_basis)
49
+ # fpylll.BKZ.reduction(mat, delta=delta)
50
+ mat_bkz = fpylll.BKZ.reduction(mat, fpylll.BKZ.Param(block_size=block_size))
51
+ return self._from_fpylll(mat_bkz)
52
+
53
+ @override
54
+ def hkz(self, lattice_basis: SquareMatrix, delta: float) -> SquareMatrix:
55
+ return self.bkz(lattice_basis, len(lattice_basis), delta)
56
+
57
+ @override
58
+ def shortest_vector(self, lattice_basis: SquareMatrix) -> Vector:
59
+ mat = self._to_fpylll(lattice_basis)
60
+ fpylll.LLL.reduction(mat)
61
+ sv = fpylll.SVP.shortest_vector(mat, pruning=False, preprocess=False)
62
+ return as_integer(sv)
63
+
64
+ @override
65
+ def closest_vector(self, lattice_basis: SquareMatrix, target_vector: Vector) -> Vector:
66
+ A = self._from_fpylll(lattice_basis)
67
+ t = target_vector.tolist()
68
+ v0 = fpylll.CVP.closest_vector(A, t)
69
+ return as_integer(v0)
70
+
71
+ @override
72
+ def hnf(self, matrix: Matrix) -> tuple[Matrix, SquareMatrix]:
73
+ mat = self._to_flint(matrix)
74
+ H, U = mat.hnf(transform=True)
75
+ return self._from_flint(H), self._from_flint(U)
@@ -0,0 +1,33 @@
1
+ from typing import override
2
+
3
+ from ..lattice._bkz import bkz
4
+ from ..lattice._hkz import hkz
5
+ from ..lattice._lll import lll
6
+ from ..lattice._svp import shortest_vector
7
+ from ..linalg._linalg import hnf
8
+ from ..typing import Matrix, SquareMatrix, Vector
9
+ from ._protocol import BackendInterface
10
+
11
+
12
+ class NativeBackend(BackendInterface):
13
+ @override
14
+ def lll(self, lattice_basis: SquareMatrix, delta: float) -> SquareMatrix:
15
+ return lll(lattice_basis, delta)
16
+
17
+ @override
18
+ def bkz(self, lattice_basis: SquareMatrix, block_size: int, delta: float) -> SquareMatrix:
19
+ _ = delta
20
+ return bkz(lattice_basis, block_size)
21
+
22
+ @override
23
+ def hkz(self, lattice_basis: SquareMatrix, delta: float) -> SquareMatrix:
24
+ _ = delta
25
+ return hkz(lattice_basis)
26
+
27
+ @override
28
+ def shortest_vector(self, lattice_basis: SquareMatrix) -> Vector:
29
+ return shortest_vector(lattice_basis)
30
+
31
+ @override
32
+ def hnf(self, matrix: Matrix) -> tuple[Matrix, SquareMatrix]:
33
+ return hnf(matrix)
@@ -0,0 +1,15 @@
1
+ from typing import Protocol
2
+
3
+ from ..typing import Matrix, SquareMatrix, Vector
4
+
5
+
6
+ class BackendInterface(Protocol):
7
+ def lll(self, lattice_basis: SquareMatrix, delta: float) -> SquareMatrix: ...
8
+
9
+ def bkz(self, lattice_basis: SquareMatrix, block_size: int, delta: float) -> SquareMatrix: ...
10
+
11
+ def hkz(self, lattice_basis: SquareMatrix, delta: float) -> SquareMatrix: ...
12
+
13
+ def shortest_vector(self, lattice_basis: SquareMatrix) -> Vector: ...
14
+
15
+ def hnf(self, matrix: Matrix) -> tuple[Matrix, SquareMatrix]: ...
pqlattice/_utils.py ADDED
@@ -0,0 +1,201 @@
1
+ from fractions import Fraction
2
+ from typing import Any
3
+
4
+ import numpy as np
5
+ from numpy.typing import ArrayLike
6
+
7
+ from .typing import Array, Matrix, Vector, is_integer, is_Matrix, is_rational, is_Vector, validate_aliases
8
+
9
+
10
+ def as_integer(obj: ArrayLike) -> Array:
11
+ """
12
+ Helper function that converts given obj to numpy's array of python's ints allowing arbitrary large elements
13
+
14
+ Parameters
15
+ ----------
16
+ obj : ArrayLike
17
+ object to be converted to numpy's array
18
+
19
+ Returns
20
+ -------
21
+ Array
22
+ numpy's array with dtype=object and elements converted to int
23
+
24
+ Examples
25
+ --------
26
+ >>> import pqlattice as pq
27
+ >>> pq.as_integer([3**100, 2**100, 5**50])
28
+ array([515377520732011331036461129765621272702107522001,
29
+ 1267650600228229401496703205376,
30
+ 88817841970012523233890533447265625], dtype=object)
31
+ """
32
+ arr = np.array(obj, dtype=object)
33
+ if arr.size == 0:
34
+ return arr
35
+ else:
36
+ return (np.vectorize(int)(arr.flat).reshape(arr.shape)).astype(object)
37
+
38
+
39
+ def as_rational(obj: ArrayLike) -> Array:
40
+ """
41
+ Helper function that converts given obj to numpy's array of python's fractions.Fraction allowing arbitrary big rational elements
42
+
43
+ Parameters
44
+ ----------
45
+ obj : ArrayLike
46
+ object to be converted to numpy's array
47
+
48
+ Returns
49
+ -------
50
+ Array
51
+ numpy's array with dtype=object and elements converted to fractions.Fraction
52
+
53
+ Examples
54
+ --------
55
+ >>> import pqlattice as pq
56
+ >>> pq.as_rational([3**100, 2**100, 5**50])
57
+ array([Fraction(515377520732011331036461129765621272702107522001, 1),
58
+ Fraction(1267650600228229401496703205376, 1),
59
+ Fraction(88817841970012523233890533447265625, 1)], dtype=object)
60
+ """
61
+ arr = np.array(obj, dtype=object)
62
+ if arr.size == 0:
63
+ return arr
64
+ else:
65
+ return (np.vectorize(Fraction)(arr.flat).reshape(arr.shape)).astype(object)
66
+
67
+
68
+ def zeros_vec(n: int) -> Vector:
69
+ return as_integer(np.zeros((n,)))
70
+
71
+
72
+ def zeros_mat(rows: int, cols: int | None = None) -> Matrix:
73
+ if cols is None:
74
+ cols = rows
75
+ return as_integer(np.zeros((rows, cols)))
76
+
77
+
78
+ @validate_aliases
79
+ def show(a: Array, max_rows: int = 10, max_cols: int = 10, val_width: int = 15):
80
+ """
81
+ Helper function that prints the numpy's array in a human redable format
82
+
83
+ Parameters
84
+ ----------
85
+ a : Array
86
+ The array to print
87
+ max_rows : int, optional
88
+ Max number of rows to display before truncating, by default 10
89
+ max_cols : int, optional
90
+ Max number of columns to display before truncating, by default 10
91
+ val_width : int, optional
92
+ Max characters per cell, by default 15. If a string representation of element is longer, it is truncated e.g 1234...5678
93
+
94
+ Examples
95
+ --------
96
+ >>> import pqlattice as pq
97
+ >>> M = pq.random.distribution.Uniform(0, 2**50, seed=0).sample_matrix(7, 5)
98
+ >>> pq.show(M)
99
+ Matrix of integers with shape: 7 x 5
100
+ ====================================
101
+ [0] [1] [2] [3] [4]
102
+ [0] 867496826243021 91162487198805 109421..4040930 806253307773444 491889324856851
103
+ [1] 313616384600182 314680579360371 213540430176889 330931104930059 222394738660569
104
+ [2] 166055160201467 743539086037546 796665326308852 712012953150114 460445890320316
105
+ [3] 996855368208390 140240390954947 210028256050344 750154124310314 141827853726696
106
+ [4] 499232256057935 320872572303314 205400145011268 110177..2031755 678794279728913
107
+ [5] 655478801553847 281048514639229 749289460799082 457570956347073 647748016542327
108
+ [6] 206336435080453 713924001980837 545175556185458 414036094290124 74247901643189
109
+ """
110
+
111
+ def format_val(val: Any) -> str:
112
+ s = str(val)
113
+ if len(s) > val_width:
114
+ mid = (val_width - 2) // 2
115
+ remainder = (val_width - 2) % 2
116
+ return f"{s[:mid]}..{s[-(mid + remainder) :]}"
117
+ return s
118
+
119
+ element_type: str = f"{a.dtype}"
120
+ if is_integer(a):
121
+ element_type = "integers"
122
+ if is_rational(a):
123
+ element_type = "rationals"
124
+
125
+ info_header = f"numpy array of {element_type} with shape: {a.shape}"
126
+
127
+ is_v = False
128
+ if is_Matrix(a):
129
+ rows, cols = a.shape
130
+ info_header = f"Matrix of {element_type} with shape: {rows} x {cols}"
131
+ elif is_Vector(a):
132
+ dim = a.shape[0]
133
+ info_header = f"Vector of {element_type} with shape: {dim}"
134
+ is_v = True
135
+ rows, cols = 1, dim
136
+ else:
137
+ print(info_header)
138
+ print("=" * len(info_header))
139
+ print(f"{a}")
140
+ return
141
+
142
+ print(info_header)
143
+ print("=" * len(info_header))
144
+
145
+ ellipsis_col_idx: int | None = None
146
+ ellipsis_row_idx: int | None = None
147
+ if rows <= max_rows:
148
+ row_indices = list(range(rows))
149
+ show_row_ellipsis = False
150
+ else:
151
+ # Top half and bottom half
152
+ r_cut = max_rows // 2
153
+ row_indices = list(range(r_cut)) + list(range(rows - r_cut, rows))
154
+ show_row_ellipsis = True
155
+ ellipsis_row_idx = r_cut
156
+
157
+ if cols <= max_cols:
158
+ col_indices = list(range(cols))
159
+ show_col_ellipsis = False
160
+ else:
161
+ c_cut = max_cols // 2
162
+ col_indices = list(range(c_cut)) + list(range(cols - c_cut, cols))
163
+ show_col_ellipsis = True
164
+ ellipsis_col_idx = c_cut
165
+
166
+ header = [""]
167
+ for i, c_idx in enumerate(col_indices):
168
+ if show_col_ellipsis and i == ellipsis_col_idx:
169
+ header.append("...")
170
+ header.append(f"[{c_idx}]")
171
+
172
+ table_data = [header]
173
+
174
+ for i, r_idx in enumerate(row_indices):
175
+ if show_row_ellipsis and i == ellipsis_row_idx:
176
+ ellipsis_row = ["..."] + ["..."] * (len(header) - 1)
177
+ table_data.append(ellipsis_row)
178
+
179
+ row_str = [f"[{r_idx}]"]
180
+
181
+ for j, c_idx in enumerate(col_indices):
182
+ if show_col_ellipsis and j == ellipsis_col_idx:
183
+ row_str.append("...")
184
+
185
+ val = a[c_idx] if is_v else a[r_idx, c_idx]
186
+ row_str.append(format_val(val))
187
+
188
+ table_data.append(row_str)
189
+
190
+ num_display_cols = len(table_data[0])
191
+ col_widths = [0] * num_display_cols
192
+
193
+ for row in table_data:
194
+ for idx, cell in enumerate(row):
195
+ col_widths[idx] = max(col_widths[idx], len(cell))
196
+
197
+ for row in table_data:
198
+ formatted_row: list[str] = []
199
+ for idx, cell in enumerate(row):
200
+ formatted_row.append(cell.rjust(col_widths[idx]))
201
+ print(" ".join(formatted_row))
@@ -0,0 +1,8 @@
1
+ from math import gcd
2
+
3
+ from ._integer import eea
4
+ from ._modintring import ModIntRing
5
+ from ._modring import cmodl, cmodr, mod, modinv, modpow
6
+ from ._primality import is_prime
7
+
8
+ __all__ = ["gcd", "eea", "mod", "cmodl", "cmodr", "modinv", "modpow", "ModIntRing", "is_prime"]
@@ -0,0 +1,55 @@
1
+ from typing import cast, overload
2
+
3
+ from .._utils import as_integer
4
+ from ..typing import Array
5
+
6
+
7
+ @overload
8
+ def eea(a: int, b: int) -> tuple[int, int, int]: ...
9
+
10
+
11
+ @overload
12
+ def eea(a: Array, b: int) -> tuple[Array, Array, Array]: ...
13
+
14
+
15
+ def eea(a: int | Array, b: int) -> tuple[int, int, int] | tuple[Array, Array, Array]:
16
+ """_summary_
17
+
18
+ Parameters
19
+ ----------
20
+ a : int | Array
21
+ _description_
22
+ b : int
23
+ _description_
24
+
25
+ Returns
26
+ -------
27
+ tuple[int, int, int] | tuple[Array, Array, Array]
28
+ _description_
29
+ """
30
+ if isinstance(a, int):
31
+ return _eea(a, b)
32
+ else:
33
+ return tuple(as_integer([_eea(cast(int, el), b) for el in a]).T)
34
+
35
+
36
+ def _eea(a: int, b: int) -> tuple[int, int, int]:
37
+ if a == 0 and b == 0:
38
+ raise ValueError("a and b can't be both zero")
39
+
40
+ old_s, s = 1, 0
41
+ old_r, r = a, b
42
+ while r != 0:
43
+ q = old_r // r
44
+ old_r, r = r, old_r - q * r
45
+ old_s, s = s, old_s - q * s
46
+
47
+ t = 0 if b == 0 else (old_r - old_s * a) // b
48
+ s = old_s
49
+ gcd = old_r
50
+ if gcd < 0:
51
+ gcd = -gcd
52
+ s = -s
53
+ t = -t
54
+
55
+ return gcd, s, t
@@ -0,0 +1,246 @@
1
+ from typing import overload
2
+
3
+ from ..typing import Array
4
+ from ._modring import cmodl, cmodr, mod, modinv, modpow
5
+
6
+
7
+ class ModIntRing:
8
+ def __init__(self, modulus: int):
9
+ """_summary_
10
+
11
+ Parameters
12
+ ----------
13
+ modulus : int
14
+ _description_
15
+
16
+ Raises
17
+ ------
18
+ ValueError
19
+ _description_
20
+ """
21
+ if abs(modulus) < 2:
22
+ raise ValueError(f"absolute value of modulus has to greater than one, given modulus: {modulus}")
23
+
24
+ self._modulus = abs(modulus)
25
+
26
+ @property
27
+ def q(self) -> int:
28
+ """_summary_
29
+
30
+ Returns
31
+ -------
32
+ _type_
33
+ _description_
34
+ """
35
+ return self._modulus
36
+
37
+ def is_zero(self, a: int) -> bool:
38
+ """_summary_
39
+
40
+ Parameters
41
+ ----------
42
+ a : int
43
+ _description_
44
+
45
+ Returns
46
+ -------
47
+ bool
48
+ _description_
49
+ """
50
+ return self.mod(a) == 0
51
+
52
+ @overload
53
+ def mod(self, a: int) -> int: ...
54
+
55
+ @overload
56
+ def mod(self, a: Array) -> Array: ...
57
+
58
+ def mod(self, a: int | Array) -> int | Array:
59
+ """_summary_
60
+
61
+ Parameters
62
+ ----------
63
+ a : int | Array
64
+ _description_
65
+
66
+ Returns
67
+ -------
68
+ int | Array
69
+ _description_
70
+ """
71
+ return mod(a, self.q)
72
+
73
+ @overload
74
+ def pow(self, a: int, r: int) -> int: ...
75
+
76
+ @overload
77
+ def pow(self, a: Array, r: int) -> Array: ...
78
+
79
+ def pow(self, a: int | Array, r: int) -> int | Array:
80
+ """_summary_
81
+
82
+ Parameters
83
+ ----------
84
+ a : int | Array
85
+ _description_
86
+ r : int
87
+ _description_
88
+
89
+ Returns
90
+ -------
91
+ int | Array
92
+ _description_
93
+ """
94
+ return self.mod(modpow(a, r, self.q))
95
+
96
+ @overload
97
+ def inv(self, a: int) -> int: ...
98
+
99
+ @overload
100
+ def inv(self, a: Array) -> Array: ...
101
+
102
+ def inv(self, a: int | Array) -> int | Array:
103
+ """_summary_
104
+
105
+ Parameters
106
+ ----------
107
+ a : int | Array
108
+ _description_
109
+
110
+ Returns
111
+ -------
112
+ int | Array
113
+ _description_
114
+ """
115
+ return self.mod(modinv(a, self.q))
116
+
117
+ @overload
118
+ def neg(self, a: int) -> int: ...
119
+
120
+ @overload
121
+ def neg(self, a: Array) -> Array: ...
122
+
123
+ def neg(self, a: int | Array) -> int | Array:
124
+ """_summary_
125
+
126
+ Parameters
127
+ ----------
128
+ a : int | Array
129
+ _description_
130
+
131
+ Returns
132
+ -------
133
+ int | Array
134
+ _description_
135
+ """
136
+ return self.mod(-a)
137
+
138
+ def add(self, a: int, b: int) -> int:
139
+ """_summary_
140
+
141
+ Parameters
142
+ ----------
143
+ a : int
144
+ _description_
145
+ b : int
146
+ _description_
147
+
148
+ Returns
149
+ -------
150
+ int
151
+ _description_
152
+ """
153
+ return self.mod(a + b)
154
+
155
+ def mul(self, a: int, b: int) -> int:
156
+ """_summary_
157
+
158
+ Parameters
159
+ ----------
160
+ a : int
161
+ _description_
162
+ b : int
163
+ _description_
164
+
165
+ Returns
166
+ -------
167
+ int
168
+ _description_
169
+ """
170
+ return self.mod(a * b)
171
+
172
+ def div(self, a: int, b: int) -> int:
173
+ """_summary_
174
+
175
+ Parameters
176
+ ----------
177
+ a : int
178
+ _description_
179
+ b : int
180
+ _description_
181
+
182
+ Returns
183
+ -------
184
+ int
185
+ _description_
186
+ """
187
+ return self.mul(a, self.inv(b))
188
+
189
+ def sub(self, a: int, b: int) -> int:
190
+ """_summary_
191
+
192
+ Parameters
193
+ ----------
194
+ a : int
195
+ _description_
196
+ b : int
197
+ _description_
198
+
199
+ Returns
200
+ -------
201
+ int
202
+ _description_
203
+ """
204
+ return self.mod(a - b)
205
+
206
+ @overload
207
+ def cmodl(self, a: int) -> int: ...
208
+
209
+ @overload
210
+ def cmodl(self, a: Array) -> Array: ...
211
+
212
+ def cmodl(self, a: int | Array) -> int | Array:
213
+ """_summary_
214
+
215
+ Parameters
216
+ ----------
217
+ a : int | Array
218
+ _description_
219
+
220
+ Returns
221
+ -------
222
+ int | Array
223
+ _description_
224
+ """
225
+ return cmodl(a, self.q)
226
+
227
+ @overload
228
+ def cmodr(self, a: int) -> int: ...
229
+
230
+ @overload
231
+ def cmodr(self, a: Array) -> Array: ...
232
+
233
+ def cmodr(self, a: int | Array) -> int | Array:
234
+ """_summary_
235
+
236
+ Parameters
237
+ ----------
238
+ a : int | Array
239
+ _description_
240
+
241
+ Returns
242
+ -------
243
+ int | Array
244
+ _description_
245
+ """
246
+ return cmodr(a, self.q)