Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/libs/fes.pyx
8815 views
1
"""
2
Binding for the FES library.
3
4
Finding solutions of systems of boolean equations by exhaustive
5
search, via the fes library. This is usually (much) faster than
6
computing a Groebner basis, except in special cases where the latter
7
is particularly easy.
8
9
The FES library is presently only able to deal with polynomials in 64
10
variables. Performing a full exhaustive search over 64 variables will
11
take a **long** time. The number of variables can be artificially
12
reduced to 64 by specializing some of them.
13
14
Note that the FES library **requires** at least of the equations to be
15
non-linear.
16
17
AUTHORS:
18
19
- Charles Bouillaguet (2012-12-20) : initial version
20
21
EXAMPLES:
22
23
Random Degree-2 System::
24
25
sage: from sage.libs.fes import exhaustive_search # optional - FES
26
sage: n = 16 # optional - FES
27
sage: R = BooleanPolynomialRing(n, 'x') # optional - FES
28
sage: solution = dict(zip(R.gens(), VectorSpace(GF(2), n).random_element())) # optional - FES
29
30
sage: F = [ R.random_element() for i in range(n+10) ] # optional - FES
31
sage: G = [ f + f.subs(solution) for f in F] # optional - FES
32
sage: sols = exhaustive_search(G) # optional - FES
33
sage: solution in sols # optional - FES
34
True
35
sage: len(sols) # optional - FES
36
1
37
38
Cylic benchmark::
39
40
sage: from sage.rings.ideal import Cyclic # optional - FES
41
sage: from sage.libs.fes import exhaustive_search # optional - FES
42
sage: n = 10 # optional - FES
43
sage: R = BooleanPolynomialRing(n, 'x') # optional - FES
44
sage: sols = exhaustive_search( Cyclic(R) ) # optional - FES
45
sage: len(sols) # optional - FES
46
1
47
sage: set(sols[0]) == set(R.gens()) # optional - FES
48
True
49
50
REFERENCES:
51
52
.. [BCCCNSY10] Charles Bouillaguet, Hsieh-Chung Chen, Chen-Mou Cheng,
53
Tung Chou, Ruben Niederhagen, Adi Shamir, and Bo-Yin Yang.
54
*Fast exhaustive search for polynomial systems in GF(2)*.
55
In Stefan Mangard and François-Xavier Standaert, editors,
56
CHES, volume 6225 of Lecture Notes in Computer Science, pages 203–218.
57
Springer, 2010. pre-print available at http://eprint.iacr.org/2010/313.pdf
58
59
"""
60
#*****************************************************************************
61
# Copyright (C) 2012 Charles Bouillaguet <[email protected]>
62
#
63
# Distributed under the terms of the GNU General Public License (GPL)
64
# as published by the Free Software Foundation; either version 2 of
65
# the License, or (at your option) any later version.
66
# http://www.gnu.org/licenses/
67
#*****************************************************************************
68
69
from libc.stdint cimport uint64_t
70
71
cdef extern from "fes_interface.h":
72
ctypedef int (*solution_callback_t)(void *, uint64_t)
73
74
void exhaustive_search_wrapper(int n, int n_eqs, int degree, int ***coeffs, solution_callback_t callback, void* callback_state, int verbose)
75
76
77
include 'sage/ext/interrupt.pxi' #sig_on(), sig_off()
78
include 'sage/ext/stdsage.pxi' #sage_calloc(), sage_free()
79
80
from sage.rings.integer import Integer
81
from sage.rings.infinity import Infinity
82
from sage.rings.finite_rings.constructor import FiniteField as GF
83
84
from sage.structure.parent cimport Parent
85
from sage.structure.sequence import Sequence
86
from sage.rings.polynomial.multi_polynomial cimport MPolynomial
87
from sage.rings.polynomial.term_order import TermOrder
88
from sage.rings.polynomial.pbori import BooleanPolynomial, BooleanPolynomialRing
89
from sage.rings.arith import binomial
90
from sage.combinat.subset import Subsets
91
92
from sage.matrix.all import *
93
from sage.modules.all import *
94
95
96
97
class InternalState:
98
verbose = False
99
sols = []
100
max_sols = 0
101
102
103
cdef int report_solution(void *_state, uint64_t i):
104
# This is the callback function which is invoked by the fes library
105
# each time a solution is found. ``i`` describes the solution (least
106
# significant bit gives the value of the first variable, most
107
# significant bit gives the value of the last variable). ``state`` is
108
# the pointer passed to the fes library initially.
109
cdef object state = <object> _state
110
state.sols.append(i)
111
if state.verbose:
112
print "fes: solution {0} / {1} found : {2:x}".format(len(state.sols), state.max_sols, i)
113
if (state.max_sols > 0 and state.max_sols >= len(state.sols)):
114
return 1 #stop the library
115
return 0 # keep going
116
117
118
def exhaustive_search(eqs, max_sols=Infinity, verbose=False):
119
r"""
120
Invokes the fes library to solve a system of boolean equation by
121
exhaustive search.
122
123
INPUT:
124
125
- ``eqs`` -- list of boolean equations
126
127
- ``max_sols`` -- stop after this many solutions are found
128
129
- ``verbose`` -- whether the library should display information about its work
130
131
NOTE:
132
133
Using this function requires the optional package FES to be installed.
134
135
EXAMPLE:
136
137
A very simple example::
138
139
sage: from sage.libs.fes import exhaustive_search # optional - FES
140
sage: R.<x,y,z> = BooleanPolynomialRing() # optional - FES
141
sage: exhaustive_search( [x*y + z + y + 1, x+y+z, x*y + y*z] ) # optional - FES
142
[{y: 0, z: 1, x: 1}]
143
144
Another very simple example::
145
146
sage: R.<x,y,z,t,w> = BooleanPolynomialRing() # optional - FES
147
sage: f = x*y*z + z + y + 1 # optional - FES
148
sage: sorted( exhaustive_search( [f, x+y+z, x*y + y*z] ) ) # optional - FES
149
[{w: 0, t: 0, y: 0, z: 1, x: 1}, {w: 0, t: 1, y: 0, z: 1, x: 1}, {w: 1, t: 0, y: 0, z: 1, x: 1}, {w: 1, t: 1, y: 0, z: 1, x: 1}]
150
151
An example with several solutions::
152
153
sage: R.<x,y,z, t> = BooleanPolynomialRing() # optional - FES
154
sage: eqs = [x*z + y*t + 1, x*y + y*z + z*t + t*x , x+y+z+1] # optional - FES
155
sage: sorted( exhaustive_search( eqs ) ) # optional - FES
156
[{t: 0, y: 1, z: 1, x: 1}, {t: 1, y: 1, z: 0, x: 0}]
157
158
We voluntarily limit the number of solutions returned by the library::
159
160
sage: eqs = [x*z + y*t + 1, x*y + y*z + z*t + t*x , x+y+z+1] # optional - FES
161
sage: exhaustive_search(eqs, max_sols=1 ) # optional - FES
162
[{t: 0, y: 1, z: 1, x: 1}]
163
164
.. SEEALSO::
165
166
This function should return the same solutions as
167
:meth:`~sage.rings.polynomial.pbori.BooleanPolynomialIdeal.variety`
168
on a :class:`~sage.rings.polynomial.pbori.BooleanPolynomialIdeal`.
169
170
.. NOTE::
171
172
The order in which the solutions are returned is implementation
173
dependent ; it may vary accross machines, and may vary accross
174
calls to the function.
175
176
"""
177
if eqs == []:
178
raise ValueError, "No equations, no solutions"
179
180
eqs = Sequence(eqs)
181
R = eqs.ring()
182
if R.base_ring() != GF(2):
183
raise ValueError, "FES only deals with equations over GF(2)"
184
185
degree = max( [f.degree() for f in eqs] )
186
if degree <= 1:
187
raise ValueError("the FES library requires equations to be non-linear")
188
n = R.ngens()
189
190
191
# ------- initialize a data-structure to communicate the equations to the library
192
cdef int ***coeffs = <int ***> sage_calloc(len(eqs), sizeof(int **))
193
for e,f in enumerate(eqs):
194
coeffs[e] = <int **> sage_calloc(degree+1, sizeof(int *))
195
for d in range(degree+1):
196
coeffs[e][d] = <int *> sage_calloc(binomial(n,d), sizeof(int))
197
198
for m in f: # we enumerate the monomials of f
199
d = m.degree()
200
temp_n = n
201
int_idx = 0
202
prev_var = -1
203
for idx in sorted(m.iterindex()): # iterate over all the variables in the monomial
204
j = idx - prev_var - 1
205
for k in range(j):
206
int_idx += binomial(temp_n-k-1, d-1)
207
prev_var = idx
208
d -= 1
209
temp_n -= 1 + j
210
coeffs[e][ m.degree() ][int_idx] = 1
211
212
internal_state = InternalState()
213
internal_state.verbose = verbose
214
internal_state.sols = []
215
if max_sols == Infinity:
216
internal_state.max_sols = 0
217
else:
218
internal_state.max_sols = max_sols
219
220
# ------- runs the library
221
sig_on()
222
exhaustive_search_wrapper(n, len(eqs), degree, coeffs, report_solution, <void *> internal_state, verbose)
223
sig_off()
224
225
# ------- frees memory occupied by the dense representation of the equations
226
if coeffs != NULL:
227
for e in range(len(eqs)):
228
if coeffs[e] != NULL:
229
for d in range(degree+1):
230
if coeffs[e][d] != NULL:
231
sage_free(coeffs[e][d])
232
sage_free(coeffs[e])
233
sage_free(coeffs)
234
235
# ------ convert (packed) solutions to suitable format
236
dict_sols = []
237
for i in internal_state.sols:
238
sol = dict([])
239
for x in R.gens():
240
sol[x] = GF(2)(i & 1)
241
i = i >> 1
242
dict_sols.append( sol )
243
244
return dict_sols
245
246
247
def find_coordinate_change(As, max_tries=64):
248
"""Tries to find a linear change of coordinates such that certain
249
coefficients of the quadratic forms As become zero. This in turn
250
makes the exhaustive search faster
251
252
EXAMPLES:
253
254
Testing that the function works::
255
256
sage: from sage.libs.fes import find_coordinate_change # optional - FES
257
sage: n = 40 # optional - FES
258
sage: K = GF(2) # optional - FES
259
sage: R = BooleanPolynomialRing(n, 'x') # optional - FES
260
sage: foo = [ MatrixSpace(GF(2), n, n).random_element() ] # optional - FES
261
sage: f = [ M + M.T for M in foo ] # optional - FES
262
sage: S = find_coordinate_change( f ) # optional - FES
263
sage: g = [ S.T*M*S for M in f ] # optional - FES
264
sage: vector ([ M[0,1] for M in g[:32]]).is_zero() # optional - FES
265
True
266
"""
267
n = As[0].nrows()
268
m = min(32, len(As))
269
system = matrix(GF(2), m, n-2)
270
S = identity_matrix(GF(2), n)
271
Bs = As
272
273
for foo in range(max_tries):
274
for i in range(m):
275
system[i] = Bs[i][0,2:]
276
RHS = vector(GF(2), m, [ Bs[i][0,1] for i in range(m) ])
277
try:
278
solution = system.solve_right( RHS )
279
S_prime = identity_matrix(GF(2), n)
280
S_prime[1,2:] = solution
281
return S*S_prime.T
282
except ValueError:
283
S.randomize()
284
while not S.is_invertible():
285
S.randomize()
286
Bs = [ S.T*M*S for M in As ]
287
print "trying again..."
288
raise ValueError, "Could not find suitable coordinate change"
289
290
291
292
293
def prepare_polynomials(f):
294
"""
295
Finds a linear combination of the equations that is faster to solve by FES
296
297
INPUT:
298
299
- ``f`` -- list of boolean equations
300
301
EXAMPLES:
302
303
We check that the function does what it is supposed to do::
304
305
sage: from sage.libs.fes import prepare_polynomials # optional - FES
306
sage: n = 35 # optional - FES
307
sage: K = GF(2) # optional - FES
308
sage: R = BooleanPolynomialRing(n, 'x') # optional - FES
309
sage: f = [ sum( [K.random_element() * R.gen(i) * R.gen(j) for i in range(n) for j in range(i)] ) \
310
+ sum( [K.random_element() * R.gen(i) for i in range(n) ] ) \
311
+ K.random_element() for l in range(n) ] # optional - FES
312
sage: g = prepare_polynomials(f) # optional - FES
313
sage: map(lambda x:x.lm(), g) # optional - FES, random
314
0
315
"""
316
if f == []:
317
return []
318
excess = len(f) - 32
319
320
if excess <= 0:
321
return f
322
323
# now, try to cancel the `excess` first monomials
324
s = Sequence(f)
325
326
# switch to degrevlex if not already there
327
R = s.ring()
328
if R.term_order() != 'degrevlex':
329
R2 = BooleanPolynomialRing( R.ngens(), R.variable_names(), order='degrevlex')
330
s = Sequence( [R2(g) for g in s] )
331
332
monomials_in_s = list( s.monomials() )
333
monomials_in_s.sort(reverse=True)
334
335
# print "fes interface: killing monomials ", monomials_in_s[:excess]
336
337
m = matrix(R.base_ring(), [ [ g.monomial_coefficient(m) for m in monomials_in_s[:excess] ] for g in s ])
338
# now find the linear combinations of the equations that kills the first `excess` monomials in all but `excess` equations
339
# todo, this is very likely suboptimal, but m.echelonize() does not returns the transformation...
340
P,L,U = m.LU()
341
S = (P*L).I
342
result = Sequence( S * vector(s) )
343
result.reverse()
344
return result
345
346