-
Notifications
You must be signed in to change notification settings - Fork 4
/
lattices.py
167 lines (133 loc) · 5.49 KB
/
lattices.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"""
Functions which solve lattice problems for use in subalgorithms of KLPT.
For KLPT there are a few spots where we need to enumerate short vectors
of lattices to ensure the smallest possible solutions to Diophantine equations.
Namely, we need close vectors to a special lattice for the strong approximation
to ensure the output bound is ~pN^3. This is accomplished with GenerateCloseVectors.
We use FPYLLL for the underlying lattice computations, which seem to outperform
Pari. We also have the ability to enumerate rather than precompute all vectors,
which is better than Pari's qfminim.
For the generation of equivalent prime norm ideals, we have an ideal basis and
we find short norm vectors of this and immediately output algebra elements.
There's probably ways to reuse the GenerateShortVectors, but there's a few
things about the prime norm elements which require special treatment so we
chose to suffer code duplication for clearer specific functions.
"""
# Sage Imports
from sage.all import vector, floor, ZZ, Matrix, randint
# fpylll imports
import fpylll
from fpylll import IntegerMatrix, CVP
from fpylll.fplll.gso import MatGSO
def solve_closest_vector_problem(lattice_basis, target):
"""
Use the fpylll library to solve the CVP problem for a given
lattice basis and target vector
"""
L = IntegerMatrix.from_matrix(lattice_basis.LLL())
v = CVP.closest_vector(L, target)
# fpylll returns a type `tuple` object
return vector(v)
def generate_short_vectors_fpyll(L, bound, count=2000):
"""
Helper function for GenerateShortVectors and
generate_small_norm_quat which builds an iterator
for short norm vectors of an LLL reduced lattice
basis.
"""
# # Move from Sage world to Fypll world
A = IntegerMatrix.from_matrix(L)
# Gram-Schmidt Othogonalization
G = MatGSO(A)
_ = G.update_gso()
# Enumeration class on G with `count`` solutions
# BEST_N_SOLUTIONS:
# Starting with the nr_solutions-th solution, every time a new solution is found
# the enumeration bound is updated to the length of the longest solution. If more
# than nr_solutions were found, the longest is dropped.
E = fpylll.Enumeration(
G, nr_solutions=count, strategy=fpylll.EvaluatorStrategy.BEST_N_SOLUTIONS
)
# We need the row count when we call enumerate
r = L.nrows()
# If enumerate finds no solutions it raises an error, so we
# wrap it in a try block
try:
# The arguments of enumerate are:
# E.enumerate(first_row, last_row, max_dist, max_dist_expo)
short_vectors = E.enumerate(0, r, bound, 0)
except Exception as e:
print(f"DEBUG [generate_short_vectors_fpyll]: No short vectors could be found...")
print(f"{e}")
short_vectors = []
return short_vectors
def generate_short_vectors(lattice_basis, bound, count=2000):
"""
Generate a generator of short vectors with norm <= `bound`
returns at most `count` vectors.
Most of the heavy lifting of this function is done by
generate_short_vectors_fpyll
"""
L = lattice_basis.LLL()
short_vectors = generate_short_vectors_fpyll(L, bound, count=count)
for _, xis in short_vectors:
# Returns values x1,x2,...xr such that
# x0*row[0] + ... + xr*row[r] = short vector
v3 = vector([ZZ(xi) for xi in xis])
v = v3 * L
yield v
def generate_close_vectors(lattice_basis, target, p, L, count=2000):
"""
Generate a generator of vectors which are close, without
bound determined by N to the `target`. The first
element of the list is the solution of the CVP.
"""
# Compute the closest element
closest = solve_closest_vector_problem(lattice_basis, target)
yield closest
# Now use short vectors below a bound to find
# close enough vectors
# Set the distance
diff = target - closest
distance = diff.dot_product(diff)
# Compute the bound from L
b0 = L // p
bound = floor((b0 + distance) + (2 * (b0 * distance).sqrt()))
short_vectors = generate_short_vectors(lattice_basis, bound, count=count)
for v in short_vectors:
yield closest + v
def generate_small_norm_quat(Ibasis, bound, count=2000):
"""
Given an ideal I and an upper bound for the scaled
norm Nrd(a) / n(I), finds elements a ∈ B such that
a has small norm.
"""
# Before starting anything, just send out the basis
# sometimes this works, and much quicker.
for bi in Ibasis:
yield bi
# Recover Quaternion algebra from IBasis for use later
B = Ibasis[0].parent()
# Write Ibasis as a matrix
Ibasis_matrix = Matrix([x.coefficient_tuple() for x in Ibasis]).transpose()
# Can't do LLL in QQ, so we move to ZZ by clearing
# the denominator
lattice_basis, _ = Ibasis_matrix._clear_denom()
L = lattice_basis.LLL()
# Move from Sage world to Fypll world
short_vectors = generate_short_vectors_fpyll(L, bound, count=count)
for _, xis in short_vectors:
# Returns values x1,x2,...xr such that
# x0*row[0] + ... + xr*row[r] = short vector
v3 = vector([ZZ(xi) for xi in xis])
# Often the very shortest are all composite
# this forces some of the latter element?
# Decide with a coin-flip?
if randint(0, 1) == 1:
v3[2] = 1
v = Ibasis_matrix * v3
yield B(v)
print(
f"WARNING [generate_small_norm_quat]: "
"Exhausted all short vectors, if you're seeing this SQISign is likely about to fail."
)