malb / algebraic_attacks (http://informatik.uni-bremen.de/~malb/blog.php)

This repository mainly holds code snippets for experimentation with algebraic attacks (and some general crypto code). The quality of this code is not 'release ready' at all. Although the code should work in general there is a lot of scratch, wrong and pathetic code in this repository. Also, some of this code dates back to my Diplomarbeit (master's thesis) and should be considered broken and outdated. By default all code listed here is released under the GPLv2+. Don't hesitate to ping me if you need something under some more permissive license like BSD-style.

Clone this repository (size: 128.5 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/malb/algebraic_attacks/
commit 35: ce280e2b1a19
parent 34: 3dd50c6be752
branch: default
fixed a very stupid bug in PRESENT which made the polynomial system unecessarily hard
Martin Albrecht / malb
2 months ago
algebraic_attacks / anf2cnf.py
    #   Introduced
1
d09b2120fe38
r"""
2
d09b2120fe38
Boolean Polynomial SAT-Solver
3
d09b2120fe38
4
abd22969c7b5
Given an ideal or polynomial system this module performs conversion to
5
abd22969c7b5
the DIMACS CNF format, calls MiniSat2 on that input and parses the
6
d09b2120fe38
output.
7
d09b2120fe38
8
d09b2120fe38
AUHTOR:
9
abd22969c7b5
10
abd22969c7b5
- Martin Albrecht - (2008-09) initial version
11
d09b2120fe38
"""
12
d09b2120fe38
13
d09b2120fe38
import commands
14
d09b2120fe38
15
048a412a8b93
from sage.rings.polynomial.pbori import BooleanPolynomial, BooleanMonomial
16
d09b2120fe38
from sage.misc.prandom import shuffle as do_shuffle
17
d09b2120fe38
18
abd22969c7b5
from sage.all import *
19
d09b2120fe38
20
d09b2120fe38
@cached_function
21
abd22969c7b5
def cached_permutations(e):
22
d09b2120fe38
    """
23
abd22969c7b5
    Cached version of ``Permutations``
24
d09b2120fe38
25
d09b2120fe38
    Since this version is cached, the input must be hash-able, e.g. a
26
d09b2120fe38
    tuple.
27
d09b2120fe38
28
d09b2120fe38
    INPUT:
29
d09b2120fe38
30
abd22969c7b5
    - ``e`` - a tuple of things to permute.
31
abd22969c7b5
32
abd22969c7b5
    EXAMPLE::
33
abd22969c7b5
34
abd22969c7b5
        sage: r1 = cached_permutations( (1,1,0,0) )
35
abd22969c7b5
        sage: r2 = cached_permutations( (1,1,0,0) )
36
d09b2120fe38
        sage: r1 is r2
37
d09b2120fe38
        True
38
d09b2120fe38
        sage: r1 = Permutations( [1,1,0,0] )
39
d09b2120fe38
        sage: r2 = Permutations( [1,1,0,0] )
40
d09b2120fe38
        sage: r1 is r2
41
d09b2120fe38
        False
42
d09b2120fe38
    """
43
d09b2120fe38
    return list(Permutations(list(e)))
44
d09b2120fe38
45
d09b2120fe38
class ANFSatSolver(SageObject):
46
d09b2120fe38
    """
47
abd22969c7b5
    Solve a boolean polynomial system using MiniSat2.
48
d09b2120fe38
    """
49
048a412a8b93
    def __init__(self, ring=None, c=None):
50
abd22969c7b5
        """
51
abd22969c7b5
        Setup the SAT-Solver and reset internal data.
52
d09b2120fe38
53
048a412a8b93
        This function is also called from :meth:`__call__()` to pass
54
048a412a8b93
        in parameters.
55
d09b2120fe38
56
d09b2120fe38
        INPUT:
57
d09b2120fe38
58
048a412a8b93
        - ``ring`` - a boolean polynomial ring
59
048a412a8b93
60
abd22969c7b5
        - ``c`` - the cutting number ``>= 2`` (default: ``4``)
61
abd22969c7b5
62
abd22969c7b5
        EXAMPLE::
63
abd22969c7b5
64
048a412a8b93
            sage: B = BooleanPolynomialRing(10,'x')
65
048a412a8b93
            sage: ANFSatSolver(B)
66
048a412a8b93
            ANFSatSolver(4) over Boolean PolynomialRing in x0, x1, x2, x3, x4, x5, x6, x7, x8, x9
67
d09b2120fe38
68
d09b2120fe38
        """
69
048a412a8b93
        self._i = 1 # the maximal index for literals
70
048a412a8b93
        self.minus = -1
71
048a412a8b93
72
048a412a8b93
        if hasattr(self,"_ring") and self._ring is not None:
73
048a412a8b93
            if ring is not None:
74
048a412a8b93
                assert(ring is self._ring)
75
048a412a8b93
        else:
76
048a412a8b93
            assert(ring is not None)
77
048a412a8b93
            self._ring = ring
78
048a412a8b93
79
d09b2120fe38
        if hasattr(self,"c") and c is None:
80
d09b2120fe38
            pass
81
d09b2120fe38
        elif c is None:
82
d09b2120fe38
            self.c = 4
83
d09b2120fe38
        else:
84
d09b2120fe38
            if c<2:
85
048a412a8b93
                raise TypeError("c must be >= 2 but is %d."%(c,))
86
d09b2120fe38
            self.c = c
87
d09b2120fe38
88
d09b2120fe38
        self.cnf_literal_map.clear_cache()
89
048a412a8b93
        self._gen_one()
90
048a412a8b93
            
91
048a412a8b93
    def _repr_(self):
92
048a412a8b93
        return "ANFSatSolver(%d) over %s"%(self.c,self._ring)
93
d09b2120fe38
94
048a412a8b93
    def __getattr__(self, name):
95
048a412a8b93
        if name == 'ring':
96
048a412a8b93
            return self._ring
97
048a412a8b93
        else:
98
048a412a8b93
            raise AttributeError("ANFSatSolver does not have an attribute names '%s'"%name)
99
d09b2120fe38
100
d09b2120fe38
    def _cnf_literal(self):
101
d09b2120fe38
        """
102
d09b2120fe38
        Return a new variable for the CNF formulas.
103
d09b2120fe38
104
abd22969c7b5
        EXAMPLE::
105
abd22969c7b5
106
d09b2120fe38
            sage: B.<x,y,z> = BooleanPolynomialRing()
107
048a412a8b93
            sage: anf2cnf = ANFSatSolver(B)
108
d09b2120fe38
            sage: anf2cnf._cnf_literal()
109
d09b2120fe38
            2
110
048a412a8b93
            sage: anf2cnf._cnf_literal()
111
048a412a8b93
            3
112
d09b2120fe38
113
abd22969c7b5
        .. note::
114
abd22969c7b5
115
abd22969c7b5
          Increases the internal literal counter.
116
d09b2120fe38
        """
117
048a412a8b93
        self._i += 1
118
048a412a8b93
        return self._i-1
119
048a412a8b93
    
120
d09b2120fe38
    @cached_method
121
d09b2120fe38
    def cnf_literal_map(self, m):
122
abd22969c7b5
        """
123
abd22969c7b5
        Given a monomial ``m`` in a boolean polynomial ring return a
124
abd22969c7b5
        tuple ``((i,),(c0,c1,...))`` where ``i`` is the index of the
125
abd22969c7b5
        monomial ``m`` and ``c0,c1,...`` are clauses which encode the
126
abd22969c7b5
        relation between the monomial ``m`` and the variables
127
abd22969c7b5
        contained in ``m``.
128
d09b2120fe38
129
abd22969c7b5
        EXAMPLE::
130
d09b2120fe38
131
d09b2120fe38
            sage: B.<x,y,z> = BooleanPolynomialRing()
132
048a412a8b93
            sage: anf2cnf = ANFSatSolver(B)
133
d09b2120fe38
            sage: anf2cnf.cnf_literal_map(B(1))
134
048a412a8b93
            (1, ((1,),))
135
d09b2120fe38
136
d09b2120fe38
            sage: anf2cnf.cnf_literal_map(x)
137
abd22969c7b5
            (2, ())
138
d09b2120fe38
139
d09b2120fe38
            sage: anf2cnf.cnf_literal_map(x*y)
140
abd22969c7b5
            (4, [(2, -4), (3, -4), (4, -2, -3)])
141
d09b2120fe38
142
d09b2120fe38
            sage: anf2cnf.cnf_literal_map(y)
143
abd22969c7b5
            (3, ())
144
d09b2120fe38
145
abd22969c7b5
        .. note:: 
146
abd22969c7b5
        
147
abd22969c7b5
          May call :meth:`_cnf_literal()`
148
d09b2120fe38
        """
149
d09b2120fe38
        minus = self.minus
150
d09b2120fe38
        cnf_literal_map = self.cnf_literal_map
151
d09b2120fe38
152
d09b2120fe38
        if isinstance(m, BooleanPolynomial):
153
d09b2120fe38
            if len(m) == 1:
154
d09b2120fe38
                m = m.lm()
155
d09b2120fe38
            else:
156
048a412a8b93
                raise TypeError("Input must be monomial.")
157
048a412a8b93
        elif not isinstance(m, BooleanMonomial):
158
048a412a8b93
            raise TypeError("Input must be of type BooleanPolynomial.")
159
d09b2120fe38
160
048a412a8b93
        if m.deg() == 0:
161
048a412a8b93
            # adding the clause that 1 has to be True
162
d09b2120fe38
            monomial = self._cnf_literal()
163
048a412a8b93
            return monomial, ((monomial,),) 
164
048a412a8b93
165
abd22969c7b5
        elif m.deg() == 1:
166
d09b2120fe38
            # a variable
167
d09b2120fe38
            monomial = self._cnf_literal()
168
abd22969c7b5
            return monomial, tuple()
169
d09b2120fe38
170
d09b2120fe38
        else:
171
d09b2120fe38
            # we need to encode the relationship between the monomial
172
d09b2120fe38
            # and its variables
173
d09b2120fe38
            variables = [cnf_literal_map(v) for v in m.variables()]
174
d09b2120fe38
            monomial = self._cnf_literal()
175
d09b2120fe38
176
d09b2120fe38
            # (a | -w) & (b | -w) & (w | -a | -b) <=> w == a*b
177
d09b2120fe38
            mon_map = []
178
d09b2120fe38
            for v,_ in variables:
179
abd22969c7b5
                mon_map.append( (v, minus * monomial) )
180
abd22969c7b5
            mon_map.append( (monomial,) + sum([(minus*v,) for v,_ in variables],tuple()) )
181
d09b2120fe38
182
abd22969c7b5
            return monomial, mon_map
183
d09b2120fe38
184
048a412a8b93
    def _gen_one(self):
185
d09b2120fe38
        """
186
d09b2120fe38
        Call this one before calling any other conversion function
187
d09b2120fe38
        """
188
048a412a8b93
        self._one_element = self._ring(1).lm()
189
048a412a8b93
        cnf_one, mon_map = self.cnf_literal_map(self._one_element)
190
d09b2120fe38
        self._cnf_one = cnf_one
191
d09b2120fe38
192
048a412a8b93
    def cnf(self, F,  shuffle=False, format='dimacs', **kwds):
193
abd22969c7b5
        """
194
abd22969c7b5
        Return CNF for ``F``.
195
d09b2120fe38
196
d09b2120fe38
        INPUT:
197
abd22969c7b5
198
abd22969c7b5
        - ``shuffle`` - shuffle output list (default: ``False``)
199
abd22969c7b5
200
abd22969c7b5
        - ``format`` - either 'dimacs' for DIMACS or ``None`` for
201
abd22969c7b5
          tuple list (default: ``dimacs``)
202
d09b2120fe38
203
d09b2120fe38
        EXAMPLE:
204
d09b2120fe38
            sage: B.<a,b,c,d> = BooleanPolynomialRing()
205
048a412a8b93
            sage: solver = ANFSatSolver(B)
206
abd22969c7b5
            sage: print solver.cnf([a*b + c + 1, d + c, a + c])
207
abd22969c7b5
            p cnf 6 16
208
abd22969c7b5
            2 -4 0
209
abd22969c7b5
            3 -4 0
210
abd22969c7b5
            4 -2 -3 0
211
abd22969c7b5
            1 0
212
abd22969c7b5
            4 5 0
213
abd22969c7b5
            -4 -5 0
214
abd22969c7b5
            1 0
215
abd22969c7b5
            5 6 1 0
216
abd22969c7b5
            -5 -6 1 0
217
abd22969c7b5
            -5 6 -1 0
218
abd22969c7b5
            5 -6 -1 0
219
abd22969c7b5
            1 0
220
abd22969c7b5
            2 5 1 0
221
abd22969c7b5
            -2 -5 1 0
222
abd22969c7b5
            -2 5 -1 0
223
abd22969c7b5
            2 -5 -1 0
224
d09b2120fe38
        """
225
d09b2120fe38
        self.__init__( **kwds)
226
d09b2120fe38
227
abd22969c7b5
        if get_verbose() >= 1:
228
048a412a8b93
            print "Parameters: c: %d, shuffle: %s"%(self.c,shuffle)
229
d09b2120fe38
230
abd22969c7b5
        C = []
231
d09b2120fe38
        for f in F:
232
abd22969c7b5
            for c in self._process_polynomial(f):
233
abd22969c7b5
                C.append(c)
234
d09b2120fe38
        if shuffle:
235
abd22969c7b5
            do_shuffle(C)
236
abd22969c7b5
237
abd22969c7b5
        if get_verbose() >= 1:
238
abd22969c7b5
            a, b = len(C),len(uniq(C))
239
abd22969c7b5
            ratio = float(a)/float(b)
240
abd22969c7b5
            print "|C|: %d, |uniq(C)|: %d, overhead: %1.3f"%(a,b,ratio - 1.0)
241
abd22969c7b5
242
abd22969c7b5
        if format == 'dimacs':
243
abd22969c7b5
            return self.to_dimacs(C)
244
abd22969c7b5
        else:
245
abd22969c7b5
            return C
246
abd22969c7b5
            
247
abd22969c7b5
    def to_dimacs(self, C):
248
abd22969c7b5
        index = max([max(map(abs,clause))for clause in C])
249
abd22969c7b5
        nclauses = len(C)
250
abd22969c7b5
251
abd22969c7b5
        out = ["p cnf %d %d\n"%(index,nclauses)]
252
abd22969c7b5
        out.extend([" ".join(map(str,clause)) + " 0\n" for clause in C])
253
abd22969c7b5
        return "".join(out)
254
d09b2120fe38
255
d09b2120fe38
    def _process_polynomial(self, f):
256
d09b2120fe38
        """
257
d09b2120fe38
        EXAMPLE:
258
d09b2120fe38
            sage: B.<a,b,c,d> = BooleanPolynomialRing()
259
048a412a8b93
            sage: solver = ANFSatSolver(B)
260
d09b2120fe38
            sage: solver._process_polynomial(a*b + c + 1)
261
d09b2120fe38
            [(2, -4), (3, -4), (4, -2, -3), (1,), (4, 5), (-4, -5)]
262
d09b2120fe38
        """
263
d09b2120fe38
        M, E = [], []
264
d09b2120fe38
        cnf_literal_map = self.cnf_literal_map
265
d09b2120fe38
        for m in f:
266
d09b2120fe38
            mbar, mon_map = cnf_literal_map( m )
267
d09b2120fe38
            E.extend( mon_map )
268
d09b2120fe38
            M.append( mbar )
269
d09b2120fe38
270
d09b2120fe38
        if len(M) > self.c:
271
d09b2120fe38
            for Mbar in self._split_xor_list(M):
272
d09b2120fe38
                E.extend(self._cnf_for_xor_list(Mbar))
273
d09b2120fe38
        else:
274
d09b2120fe38
            E.extend( self._cnf_for_xor_list(M) )
275
d09b2120fe38
        return E
276
d09b2120fe38
277
abd22969c7b5
    def _split_xor_list(self, monomial_list):
278
d09b2120fe38
        """
279
abd22969c7b5
        Splits a list of monomials into sublists and introduces
280
abd22969c7b5
        connection variables. 
281
abd22969c7b5
282
abd22969c7b5
        INPUT:
283
abd22969c7b5
284
abd22969c7b5
        - ``monomial_list`` - a list of indices already registered
285
abd22969c7b5
          with ``self``.
286
abd22969c7b5
287
abd22969c7b5
        EXAMPLE::
288
abd22969c7b5
289
048a412a8b93
            sage: B = BooleanPolynomialRing(3,'x')
290
048a412a8b93
            sage: asolver = ANFSatSolver(B)
291
abd22969c7b5
            sage: l = [asolver._cnf_literal() for _ in range(5)]; l
292
048a412a8b93
            [2, 3, 4, 5, 6]
293
abd22969c7b5
            sage: asolver._split_xor_list(l)
294
048a412a8b93
            [[2, 3, 7], [7, 4, 5, 8], [8, 6]]
295
d09b2120fe38
        """
296
d09b2120fe38
        c = self.c
297
d09b2120fe38
298
abd22969c7b5
        nm = len(monomial_list)
299
abd22969c7b5
        part_length =  ceil((c-2)/ZZ(nm) * nm)
300
abd22969c7b5
        M = []
301
abd22969c7b5
302
abd22969c7b5
        new_variables = []
303
abd22969c7b5
        for j in range(0, nm, part_length):
304
abd22969c7b5
            m =  new_variables + monomial_list[j:j+part_length]
305
abd22969c7b5
            if (j + part_length) < nm:
306
abd22969c7b5
                new_variables = [self._cnf_literal()]
307
abd22969c7b5
                m += new_variables
308
abd22969c7b5
            M.append(m)
309
abd22969c7b5
        return M
310
d09b2120fe38
311
d09b2120fe38
    def _cnf_for_xor_list(self, M):
312
d09b2120fe38
        minus = self.minus
313
d09b2120fe38
314
d09b2120fe38
        E = []
315
d09b2120fe38
316
d09b2120fe38
        if self._cnf_one in M:
317
d09b2120fe38
            M.remove(self._cnf_one)
318
d09b2120fe38
        else:
319
048a412a8b93
            mbar, mon_map = self.cnf_literal_map(self._one_element)
320
d09b2120fe38
            M.append(mbar)
321
d09b2120fe38
            E.extend(mon_map)
322
d09b2120fe38
323
d09b2120fe38
        ll = len( M )
324
d09b2120fe38
        ll2 = ll + 1 if ll%2 == 0 else ll
325
d09b2120fe38
326
d09b2120fe38
        for l in range(0, ll2, 2):
327
d09b2120fe38
            p = tuple([1]*l + [0]*(ll-l))
328
abd22969c7b5
            for p in cached_permutations(p):
329
abd22969c7b5
                E.append( sum([(minus**p[i] * M[i],) for i in range(ll)],tuple()) )
330
d09b2120fe38
        return E
331
d09b2120fe38
            
332
d09b2120fe38
    def __call__(self, F, **kwds):
333
d09b2120fe38
        """
334
d09b2120fe38
        EXAMPLE:
335
d09b2120fe38
            sage: sr = mq.SR(1,1,1,4,gf2=True)
336
d09b2120fe38
            sage: F,s = sr.polynomial_system()
337
d09b2120fe38
            sage: B = BooleanPolynomialRing(F.ring().ngens(), F.ring().variable_names())
338
d09b2120fe38
            sage: F = [B(f) for f in F if B(f)]
339
048a412a8b93
            sage: solver = ANFSatSolver(B)
340
abd22969c7b5
            sage: solution, t = solver(F)
341
abd22969c7b5
            sage: solution
342
abd22969c7b5
            {k001: 0, k002: 0, s003: 1, k000: 1, k003: 0, 
343
abd22969c7b5
             x103: 0, w100: 0, w101: 0, w102: 1, s000: 1, 
344
abd22969c7b5
             w103: 1, s002: 1, s001: 1, x102: 1, x101: 1, 
345
abd22969c7b5
             x100: 1, k103: 0, k101: 0, k102: 0, k100: 1}
346
d09b2120fe38
347
d09b2120fe38
            sage: B.ideal(F).groebner_basis()
348
abd22969c7b5
            [k100 + k003 + 1, 
349
abd22969c7b5
             k101 + k003, 
350
abd22969c7b5
             k102, 
351
abd22969c7b5
             k103 + k003, 
352
abd22969c7b5
             x100 + 1, 
353
abd22969c7b5
             ...
354
abd22969c7b5
             k000 + k003 + 1, 
355
abd22969c7b5
             k001, 
356
abd22969c7b5
             k002 + k003]
357
d09b2120fe38
        """
358
abd22969c7b5
        fn = tmp_filename()
359
abd22969c7b5
        have_poly = False
360
d09b2120fe38
361
abd22969c7b5
        if isinstance(F, str):
362
abd22969c7b5
            fh = open(fn,"w")
363
abd22969c7b5
            fh.write(self.cnf(F, format='dimacs', **kwds))
364
abd22969c7b5
            fh.close()
365
d09b2120fe38
366
abd22969c7b5
        else:
367
abd22969c7b5
            p = iter(F).next()
368
abd22969c7b5
            if isinstance(p, BooleanPolynomial):
369
abd22969c7b5
                have_poly = True
370
abd22969c7b5
                self._ring = p.parent()
371
abd22969c7b5
372
abd22969c7b5
                fh = open(fn,"w")
373
abd22969c7b5
                fh.write(self.cnf(F, format='dimacs', **kwds))
374
abd22969c7b5
                fh.close()
375
abd22969c7b5
            elif isinstance(p, tuple):
376
abd22969c7b5
                fh = open(fn,"w")
377
abd22969c7b5
                fh.write(self.to_dimacs(F))
378
abd22969c7b5
                fh.close()
379
abd22969c7b5
            else:
380
abd22969c7b5
                raise TypeError("Type '%s' not supported."%(type(p),))
381
abd22969c7b5
382
abd22969c7b5
        # call MiniSat
383
d09b2120fe38
        on = tmp_filename()
384
d09b2120fe38
        s =  commands.getoutput("minisat2 %s %s"%(fn,on))
385
d09b2120fe38
386
abd22969c7b5
        if get_verbose() >= 2:
387
abd22969c7b5
            print 
388
abd22969c7b5
            print 
389
d09b2120fe38
            print s
390
d09b2120fe38
391
d09b2120fe38
        s = s.splitlines()
392
d09b2120fe38
        for l in s:
393
d09b2120fe38
            if "CPU time" in l:
394
d09b2120fe38
                t = float(l[l.index(":")+1:l.rindex("s")])
395
d09b2120fe38
                break
396
d09b2120fe38
397
d09b2120fe38
        res =  open(on).read()
398
d09b2120fe38
        if res.startswith("UNSAT"):
399
70d6b52f3812
            return False, t
400
d09b2120fe38
        res = res[4:]
401
d09b2120fe38
        res = map(int, res.split(" "))
402
d09b2120fe38
403
abd22969c7b5
        # parse result
404
abd22969c7b5
        if not have_poly:
405
abd22969c7b5
            return res, t
406
abd22969c7b5
        else:
407
abd22969c7b5
            return self.map_solution_to_variables(res), t
408
abd22969c7b5
409
abd22969c7b5
    def map_solution_to_variables(self, res, gd=None):
410
abd22969c7b5
        """
411
abd22969c7b5
        """
412
abd22969c7b5
        if gd is None:
413
abd22969c7b5
            gens = self._ring.gens()
414
abd22969c7b5
            gd = {}
415
abd22969c7b5
            cnf_literal_map = self.cnf_literal_map
416
abd22969c7b5
            for gen in gens:
417
abd22969c7b5
                try:
418
abd22969c7b5
                    im, _ = cnf_literal_map(gen.lm())
419
abd22969c7b5
                    gd[im] = gen
420
abd22969c7b5
                except KeyError:
421
abd22969c7b5
                    pass
422
abd22969c7b5
423
d09b2120fe38
        solution = {}
424
d09b2120fe38
        for r in res:
425
d09b2120fe38
            if abs(r) in gd:
426
d09b2120fe38
                if r>0:
427
d09b2120fe38
                    solution[gd[abs(r)]] = 1
428
d09b2120fe38
                else:
429
d09b2120fe38
                    solution[gd[abs(r)]] = 0
430
abd22969c7b5
        return solution
431
d09b2120fe38
432
d09b2120fe38
def beta(F):
433
d09b2120fe38
    B = F.ring()
434
d09b2120fe38
    
435
d09b2120fe38
    n = F.nvariables()
436
d09b2120fe38
    m = F.ngens()
437
d09b2120fe38
    k = sum([len(f) for f in F])
438
d09b2120fe38
    d = max([f.total_degree() for f in F])
439
d09b2120fe38
    return k / float(m * sum([binomial(n,i) for i in range(0,d+1)]))
440
d09b2120fe38
441
d09b2120fe38