Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/crypto/mq/sbox.py
4078 views
1
r"""
2
S-Boxes and Their Algebraic Representations
3
"""
4
5
from sage.combinat.integer_vector import IntegerVectors
6
from sage.matrix.constructor import Matrix
7
from sage.misc.misc_c import prod as mul
8
from sage.modules.free_module_element import vector
9
from sage.rings.finite_rings.element_ext_pari import is_FiniteFieldElement
10
from sage.rings.finite_rings.constructor import FiniteField as GF
11
from sage.rings.ideal import FieldIdeal, Ideal
12
from sage.rings.integer_ring import ZZ
13
from sage.rings.integer import Integer
14
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
15
from sage.structure.sage_object import SageObject
16
17
class SBox(SageObject):
18
r"""
19
A substitution box or S-box is one of the basic components of
20
symmetric key cryptography. In general, an S-box takes ``m`` input
21
bits and transforms them into ``n`` output bits. This is called an
22
``mxn`` S-box and is often implemented as a lookup table. These
23
S-boxes are carefully chosen to resist linear and differential
24
cryptanalysis [Heys02]_.
25
26
This module implements an S-box class which allows an algebraic
27
treatment.
28
29
EXAMPLE:
30
31
We consider the S-box of the block cipher PRESENT [PRESENT07]_::
32
33
sage: S = mq.SBox(12,5,6,11,9,0,10,13,3,14,15,8,4,7,1,2); S
34
(12, 5, 6, 11, 9, 0, 10, 13, 3, 14, 15, 8, 4, 7, 1, 2)
35
sage: S(1)
36
5
37
38
Note that by default bits are interpreted in big endian
39
order. This is not consistent with the rest of Sage, which has a
40
strong bias towards little endian, but is consistent with most
41
cryptographic literature::
42
43
sage: S([0,0,0,1])
44
[0, 1, 0, 1]
45
46
sage: S = mq.SBox(12,5,6,11,9,0,10,13,3,14,15,8,4,7,1,2, big_endian=False)
47
sage: S(1)
48
5
49
sage: S([0,0,0,1])
50
[1, 1, 0, 0]
51
52
53
Now we construct an ``SBox`` object for the 4-bit small scale AES
54
S-Box (cf. :mod:`sage.crypto.mq.sr`)::
55
56
sage: sr = mq.SR(1,1,1,4, allow_zero_inversions=True)
57
sage: S = mq.SBox([sr.sub_byte(e) for e in list(sr.k)])
58
sage: S
59
(6, 5, 2, 9, 4, 7, 3, 12, 14, 15, 10, 0, 8, 1, 13, 11)
60
61
REFERENCES:
62
63
.. [Heys02] H. Heys *A Tutorial on Linear and Differential
64
Cryptanalysis* ; 2002' available at
65
http://www.engr.mun.ca/~howard/PAPERS/ldc_tutorial.pdf
66
67
.. [PRESENT07] A. Bogdanov, L. Knudsen, G. Leander, C. Paar,
68
A. Poschmann, M. Robshaw, Y. Seurin, C. Vikkelsoe *PRESENT: An
69
Ultra-Lightweight Block Cipher*; in Proceedings of CHES 2007;
70
LNCS 7427; pp. 450-466; Springer Verlag 2007; available at
71
http://www.crypto.rub.de/imperia/md/content/texte/publications/conferences/present_ches2007.pdf
72
"""
73
74
def __init__(self, *args, **kwargs):
75
"""
76
Construct a substitution box (S-box) for a given lookup table
77
`S`.
78
79
INPUT:
80
81
- ``S`` - a finite iterable defining the S-box with integer or
82
finite field elements
83
84
- ``big_endian`` - controls whether bits shall be ordered in
85
big endian order (default: ``True``)
86
87
EXAMPLE:
88
89
We construct a 3-bit S-box where e.g. the bits (0,0,1) are
90
mapped to (1,1,1).::
91
92
sage: S = mq.SBox(7,6,0,4,2,5,1,3); S
93
(7, 6, 0, 4, 2, 5, 1, 3)
94
95
sage: S(0)
96
7
97
98
TESTS::
99
100
sage: S = mq.SBox()
101
Traceback (most recent call last):
102
...
103
TypeError: No lookup table provided.
104
sage: S = mq.SBox(1, 2, 3)
105
Traceback (most recent call last):
106
...
107
TypeError: Lookup table length is not a power of 2.
108
sage: S = mq.SBox(5, 6, 0, 3, 4, 2, 1, 2)
109
sage: S.n
110
3
111
"""
112
if "S" in kwargs:
113
S = kwargs["S"]
114
elif len(args) == 1:
115
S = args[0]
116
elif len(args) > 1:
117
S = args
118
else:
119
raise TypeError("No lookup table provided.")
120
121
_S = []
122
for e in S:
123
if is_FiniteFieldElement(e):
124
e = e.polynomial().change_ring(ZZ).subs( e.parent().characteristic() )
125
_S.append(e)
126
S = _S
127
128
if not ZZ(len(S)).is_power_of(2):
129
raise TypeError("Lookup table length is not a power of 2.")
130
self._S = S
131
132
self.m = ZZ(len(S)).exact_log(2)
133
self.n = ZZ(max(S)).nbits()
134
self._F = GF(2)
135
self._big_endian = kwargs.get("big_endian",True)
136
137
def _repr_(self):
138
"""
139
EXAMPLE::
140
141
sage: mq.SBox(7,6,0,4,2,5,1,3) #indirect doctest
142
(7, 6, 0, 4, 2, 5, 1, 3)
143
"""
144
return "(" + ", ".join(map(str,list(self))) + ")"
145
146
def __len__(self):
147
"""
148
Return the length of input bit strings.
149
150
EXAMPLE::
151
152
sage: len(mq.SBox(7,6,0,4,2,5,1,3))
153
3
154
"""
155
return self.m
156
157
def __cmp__(self, other):
158
"""
159
S-boxes are considered to be equal if all construction
160
parameters match.
161
162
EXAMPLE::
163
164
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
165
sage: loads(dumps(S)) == S
166
True
167
"""
168
return cmp((self._S,self._big_endian), (other._S,self._big_endian))
169
170
def to_bits(self, x, n=None):
171
"""
172
Return bitstring of length ``n`` for integer ``x``. The
173
returned bitstring is guaranteed to have length ``n``.
174
175
INPUT:
176
177
- ``x`` - an integer
178
179
- ``n`` - bit length (optional)
180
181
EXAMPLE::
182
183
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
184
sage: S.to_bits(6)
185
[1, 1, 0]
186
187
sage: S.to_bits( S(6) )
188
[0, 0, 1]
189
190
sage: S( S.to_bits( 6 ) )
191
[0, 0, 1]
192
"""
193
if n is None and self.m == self.n:
194
n = self.n
195
196
if self._big_endian:
197
swp = lambda x: list(reversed(x))
198
else:
199
swp = lambda x: x
200
return swp(self._rpad( map(self._F,ZZ(x).digits(2)), n ))
201
202
def from_bits(self, x, n=None):
203
"""
204
Return integer for bitstring ``x`` of length ``n``.
205
206
INPUT:
207
208
- ``x`` - a bitstring
209
210
- ``n`` - bit length (optional)
211
212
EXAMPLE::
213
214
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
215
sage: S.from_bits( [1,1,0])
216
6
217
218
sage: S( S.from_bits( [1,1,0] ) )
219
1
220
sage: S.from_bits( S( [1,1,0] ) )
221
1
222
"""
223
if n is None and self.m == self.n:
224
n = self.m
225
226
if self._big_endian:
227
swp = lambda x: list(reversed(x))
228
else:
229
swp = lambda x: x
230
231
return ZZ( map(ZZ, self._rpad(swp(x), n)), 2)
232
233
def _rpad(self,x, n=None):
234
"""
235
Right pads ``x`` such that ``len(x) == n``.
236
237
EXAMPLE::
238
239
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
240
sage: S._rpad([1,1])
241
[1, 1, 0]
242
"""
243
if n is None and self.m == self.n:
244
n = self.n
245
return x + [self._F(0)]*(n-len(x))
246
247
def __call__(self, X):
248
"""
249
Apply substitution to ``X``.
250
251
If ``X`` is a list, it is interpreted as a sequence of bits
252
depending on the bit order of this S-box.
253
254
INPUT:
255
256
- ``X`` - either an integer, a tuple of `\GF{2}` elements of
257
length ``len(self)`` or a finite field element in
258
`\GF{2^n}`. As a last resort this function tries to convert
259
``X`` to an integer.
260
261
EXAMPLE::
262
263
sage: S = mq.SBox([7,6,0,4,2,5,1,3])
264
sage: S(7)
265
3
266
267
sage: S((0,2,3))
268
[0, 1, 1]
269
270
sage: S[0]
271
7
272
273
sage: S[(0,0,1)]
274
[1, 1, 0]
275
276
sage: k.<a> = GF(2^3)
277
sage: S(a^2)
278
a
279
280
sage: S(QQ(3))
281
4
282
283
sage: S([1]*10^6)
284
Traceback (most recent call last):
285
...
286
TypeError: Cannot apply SBox to provided element.
287
288
sage: S(1/2)
289
Traceback (most recent call last):
290
...
291
TypeError: Cannot apply SBox to 1/2.
292
293
sage: S = mq.SBox(3, 0, 1, 3, 1, 0, 2, 2)
294
sage: S(0)
295
3
296
sage: S([0,0,0])
297
[1, 1]
298
"""
299
if isinstance(X, (int, long, Integer)):
300
return self._S[ZZ(X)]
301
302
try:
303
from sage.modules.free_module_element import vector
304
K = X.parent()
305
if K.order() == 2**self.n:
306
X = vector(X)
307
else:
308
raise TypeError
309
if not self._big_endian:
310
X = list(reversed(X))
311
else:
312
X = list(X)
313
X = ZZ(map(ZZ,X),2)
314
out = self.to_bits(self._S[X], self.n)
315
if self._big_endian:
316
out = list(reversed(out))
317
return K(vector(GF(2),out))
318
except (AttributeError, TypeError):
319
pass
320
321
try:
322
if len(X) == self.m:
323
if self._big_endian:
324
X = list(reversed(X))
325
X = ZZ(map(ZZ,X),2)
326
out = self._S[X]
327
return self.to_bits(out,self.n)
328
except TypeError:
329
pass
330
331
try:
332
return self._S[ZZ(X)]
333
except TypeError:
334
pass
335
336
if len(str(X)) > 50:
337
raise TypeError, "Cannot apply SBox to provided element."
338
else:
339
raise TypeError, "Cannot apply SBox to %s."%(X,)
340
341
def __getitem__(self, X):
342
"""
343
See :meth:`SBox.__call__`.
344
345
EXAMPLE::
346
347
sage: S = mq.SBox([7,6,0,4,2,5,1,3])
348
sage: S[7]
349
3
350
"""
351
return self(X)
352
353
def is_permutation(self):
354
"""
355
Return ``True`` if this S-Box is a permutation.
356
357
EXAMPLE::
358
359
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
360
sage: S.is_permutation()
361
True
362
363
sage: S = mq.SBox(3,2,0,0,2,1,1,3)
364
sage: S.is_permutation()
365
False
366
"""
367
if self.m != self.n:
368
return False
369
return len(set([self(i) for i in range(2**self.m)])) == 2**self.m
370
371
def __iter__(self):
372
"""
373
EXAMPLE::
374
375
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
376
sage: [e for e in S]
377
[7, 6, 0, 4, 2, 5, 1, 3]
378
"""
379
for i in xrange(2**self.m):
380
yield self(i)
381
382
def difference_distribution_matrix(self):
383
"""
384
Return difference distribution matrix ``A`` for this S-box.
385
386
The rows of ``A`` encode the differences ``Delta I`` of the
387
input and the columns encode the difference ``Delta O`` for
388
the output. The bits are ordered according to the endianess of
389
this S-box. The value at ``A[Delta I,Delta O]`` encodes how
390
often ``Delta O`` is the actual output difference given
391
``Delta I`` as input difference.
392
393
See [Heys02]_ for an introduction to differential
394
cryptanalysis.
395
396
EXAMPLE::
397
398
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
399
sage: S.difference_distribution_matrix()
400
[8 0 0 0 0 0 0 0]
401
[0 2 2 0 2 0 0 2]
402
[0 0 2 2 0 0 2 2]
403
[0 2 0 2 2 0 2 0]
404
[0 2 0 2 0 2 0 2]
405
[0 0 2 2 2 2 0 0]
406
[0 2 2 0 0 2 2 0]
407
[0 0 0 0 2 2 2 2]
408
"""
409
m = self.m
410
n = self.n
411
412
nrows = 1<<m
413
ncols = 1<<n
414
415
A = Matrix(ZZ, nrows, ncols)
416
417
for i in range(nrows):
418
si = self(i)
419
for di in range(nrows):
420
A[ di , si^self(i^di)] += 1
421
return A
422
423
def maximal_difference_probability_absolute(self):
424
"""
425
Return the difference probability of the difference with the
426
highest probability in absolute terms, i.e. how often it
427
occurs in total.
428
429
EXAMPLE::
430
431
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
432
sage: S.maximal_difference_probability_absolute()
433
2
434
435
.. note::
436
437
This code is mainly called internally.
438
"""
439
A = self.difference_distribution_matrix().__copy__()
440
A[0,0] = 0
441
return max(map(abs, A.list()))
442
443
def maximal_difference_probability(self):
444
r"""
445
Return the difference probability of the difference with the
446
highest probability in the range between 0.0 and 1.0
447
indicating 0\% or 100\% respectively.
448
449
EXAMPLE::
450
451
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
452
sage: S.maximal_difference_probability()
453
0.25
454
"""
455
return self.maximal_difference_probability_absolute()/(2.0**self.n)
456
457
def linear_approximation_matrix(self):
458
"""
459
Return linear approximation matrix ``A`` for this S-box.
460
461
Let ``i_b`` be the ``b``-th bit of ``i`` and ``o_b`` the
462
``b``-th bit of ``o``. Then ``v = A[i,o]`` encodes the bias of
463
the equation ``sum( i_b * x_i ) = sum( o_b * y_i )`` if
464
``x_i`` and ``y_i`` represent the input and output variables
465
of the S-box.
466
467
See [Heys02]_ for an introduction to linear cryptanalysis.
468
469
EXAMPLE::
470
471
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
472
sage: S.linear_approximation_matrix()
473
[ 4 0 0 0 0 0 0 0]
474
[ 0 0 0 0 2 2 2 -2]
475
[ 0 0 -2 -2 -2 2 0 0]
476
[ 0 0 -2 2 0 0 -2 -2]
477
[ 0 2 0 2 -2 0 2 0]
478
[ 0 -2 0 2 0 2 0 2]
479
[ 0 -2 -2 0 0 -2 2 0]
480
[ 0 -2 2 0 -2 0 0 -2]
481
482
According to this matrix the first bit of the input is equal
483
to the third bit of the output 6 out of 8 times::
484
485
sage: for i in srange(8): print S.to_bits(i)[0] == S.to_bits(S(i))[2]
486
False
487
True
488
True
489
True
490
False
491
True
492
True
493
True
494
"""
495
try:
496
return self._linear_approximation_matrix
497
except AttributeError:
498
pass
499
500
m = self.m
501
n = self.n
502
503
nrows = 1<<m
504
ncols = 1<<n
505
506
from sage.crypto.boolean_function import BooleanFunction
507
508
B = BooleanFunction(self.m)
509
L = []
510
for j in xrange(ncols):
511
for i in xrange(nrows):
512
B[i] = (self(i)&j).popcount()
513
L.append(B.walsh_hadamard_transform())
514
515
A = Matrix(ZZ, ncols, nrows, L)
516
A = -A.transpose()/2
517
518
self._linear_approximation_matrix = A
519
return A
520
521
def maximal_linear_bias_absolute(self):
522
"""
523
Return maximal linear bias, i.e. how often the linear
524
approximation with the highest bias is true or false minus
525
`2^{n-1}`.
526
527
EXAMPLE::
528
529
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
530
sage: S.maximal_linear_bias_absolute()
531
2
532
"""
533
A = self.linear_approximation_matrix().__copy__()
534
A[0,0] = 0
535
return max(map(abs, A.list()))
536
537
def maximal_linear_bias_relative(self):
538
"""
539
Return maximal bias of all linear approximations of this
540
S-box.
541
542
EXAMPLE::
543
544
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
545
sage: S.maximal_linear_bias_relative()
546
0.25
547
"""
548
return self.maximal_linear_bias_absolute()/(2.0**self.m)
549
550
def ring(self):
551
"""
552
Create, return and cache a polynomial ring for S-box
553
polynomials.
554
555
EXAMPLE::
556
557
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
558
sage: S.ring()
559
Multivariate Polynomial Ring in x0, x1, x2, y0, y1, y2 over Finite Field of size 2
560
"""
561
try:
562
return self._ring
563
except AttributeError:
564
pass
565
566
m = self.m
567
n = self.n
568
569
X = range(m)
570
Y = range(n)
571
self._ring = PolynomialRing(self._F, m+n, ["x%d"%i for i in X] + ["y%d"%i for i in Y])
572
return self._ring
573
574
def solutions(self, X=None, Y=None):
575
"""
576
Return a dictionary of solutions to this S-box.
577
578
INPUT:
579
580
- ``X`` - input variables (default: ``None``)
581
582
- ``Y`` - output variables (default: ``None``)
583
584
EXAMPLE::
585
586
sage: S = mq.SBox([7,6,0,4,2,5,1,3])
587
sage: F = S.polynomials()
588
sage: s = S.solutions()
589
sage: any(f.subs(_s) for f in F for _s in s)
590
False
591
"""
592
if X is None and Y is None:
593
P = self.ring()
594
gens = P.gens()
595
else:
596
P = X[0].parent()
597
gens = X + Y
598
599
m = self.m
600
n = self.n
601
602
solutions = []
603
for i in range(1<<m):
604
solution = self.to_bits(i,m) + self( self.to_bits(i,m) )
605
solutions.append( dict(zip(gens, solution)) )
606
607
return solutions
608
609
def polynomials(self, X=None, Y=None, degree=2, groebner=False):
610
"""
611
Return a list of polynomials satisfying this S-box.
612
613
First, a simple linear fitting is performed for the given
614
``degree`` (cf. for example [BC03]_). If ``groebner=True`` a
615
Groebner basis is also computed for the result of that
616
process.
617
618
INPUT:
619
620
- ``X`` - input variables
621
622
- ``Y`` - output variables
623
624
- ``degree`` - integer > 0 (default: ``2``)
625
626
- ``groebner`` - calculate a reduced Groebner basis of the
627
spanning polynomials to obtain more polynomials (default:
628
``False``)
629
630
EXAMPLES::
631
632
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
633
sage: P = S.ring()
634
635
By default, this method returns an indirect representation::
636
637
sage: S.polynomials()
638
[x0*x2 + x1 + y1 + 1,
639
x0*x1 + x1 + x2 + y0 + y1 + y2 + 1,
640
x0*y1 + x0 + x2 + y0 + y2,
641
x0*y0 + x0*y2 + x1 + x2 + y0 + y1 + y2 + 1,
642
x1*x2 + x0 + x1 + x2 + y2 + 1,
643
x0*y0 + x1*y0 + x0 + x2 + y1 + y2,
644
x0*y0 + x1*y1 + x1 + y1 + 1,
645
x1*y2 + x1 + x2 + y0 + y1 + y2 + 1,
646
x0*y0 + x2*y0 + x1 + x2 + y1 + 1,
647
x2*y1 + x0 + y1 + y2,
648
x2*y2 + x1 + y1 + 1,
649
y0*y1 + x0 + x2 + y0 + y1 + y2,
650
y0*y2 + x1 + x2 + y0 + y1 + 1,
651
y1*y2 + x2 + y0]
652
653
We can get a direct representation by computing a
654
lexicographical Groebner basis with respect to the right
655
variable ordering, i.e. a variable ordering where the output
656
bits are greater than the input bits::
657
658
sage: P.<y0,y1,y2,x0,x1,x2> = PolynomialRing(GF(2),6,order='lex')
659
sage: S.polynomials([x0,x1,x2],[y0,y1,y2], groebner=True)
660
[y0 + x0*x1 + x0*x2 + x0 + x1*x2 + x1 + 1,
661
y1 + x0*x2 + x1 + 1,
662
y2 + x0 + x1*x2 + x1 + x2 + 1]
663
664
REFERENCES:
665
666
.. [BC03] A. Biryukov and C. D. Canniere *Block Ciphers and
667
Systems of Quadratic Equations*; in Proceedings of Fast
668
Software Encryption 2003; LNCS 2887; pp. 274-289,
669
Springer-Verlag 2003.
670
"""
671
def nterms(nvars, deg):
672
"""
673
Return the number of monomials possible up to a given
674
degree.
675
676
INPUT:
677
678
- ``nvars`` - number of variables
679
680
- ``deg`` - degree
681
682
TESTS::
683
684
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
685
sage: F = S.polynomials(degree=3) # indirect doctest
686
"""
687
total = 1
688
divisor = 1
689
var_choices = 1
690
691
for d in xrange(1, deg+1):
692
var_choices *= (nvars - d + 1)
693
divisor *= d
694
total += var_choices/divisor
695
return total
696
697
m = self.m
698
n = self.n
699
F = self._F
700
701
if X is None and Y is None:
702
P = self.ring()
703
X = P.gens()[:m]
704
Y = P.gens()[m:]
705
else:
706
P = X[0].parent()
707
708
gens = X+Y
709
710
bits = []
711
for i in range(1<<m):
712
bits.append( self.to_bits(i,m) + self(self.to_bits(i,m)) )
713
714
ncols = (1<<m)+1
715
716
A = Matrix(P, nterms(m+n, degree), ncols)
717
718
exponents = []
719
for d in range(degree+1):
720
exponents += IntegerVectors(d, max_length=m+n, min_length=m+n, min_part=0, max_part=1).list()
721
722
row = 0
723
for exponent in exponents:
724
A[row,ncols-1] = mul([gens[i]**exponent[i] for i in range(len(exponent))])
725
for col in range(1<<m):
726
A[row,col] = mul([bits[col][i] for i in range(len(exponent)) if exponent[i]])
727
row +=1
728
729
for c in range(ncols):
730
A[0,c] = 1
731
732
RR = A.echelon_form(algorithm='row_reduction')
733
734
# extract spanning stet
735
gens = (RR.column(ncols-1)[1<<m:]).list()
736
737
if not groebner:
738
return gens
739
740
FI = set(FieldIdeal(P).gens())
741
I = Ideal(gens + list(FI))
742
gb = I.groebner_basis()
743
744
gens = []
745
for f in gb:
746
if f not in FI: # filter out field equations
747
gens.append(f)
748
return gens
749
750
def interpolation_polynomial(self, k=None):
751
r"""
752
Return a univariate polynomial over an extension field
753
representing this S-box.
754
755
If ``m`` is the input length of this S-box then the extension
756
field is of degree ``m``.
757
758
If the output length does not match the input length then a
759
``TypeError`` is raised.
760
761
INPUT:
762
763
- ``k`` - an instance of `\GF{2^m}` (default: ``None``)
764
765
EXAMPLE::
766
767
sage: S = mq.SBox(7,6,0,4,2,5,1,3)
768
sage: f = S.interpolation_polynomial()
769
sage: f
770
x^6 + a*x^5 + (a + 1)*x^4 + (a^2 + a + 1)*x^3
771
+ (a^2 + 1)*x^2 + (a + 1)*x + a^2 + a + 1
772
773
sage: a = f.base_ring().gen()
774
775
sage: f(0), S(0)
776
(a^2 + a + 1, 7)
777
778
sage: f(a^2 + 1), S(5)
779
(a^2 + 1, 5)
780
"""
781
if self.m != self.n:
782
raise TypeError, "Lagrange interpolation only supported if self.m == self.n."
783
784
if k is None:
785
k = GF(2**self.m,'a')
786
l = []
787
for i in xrange(2**self.m):
788
i = self.to_bits(i, self.m)
789
o = self(i)
790
if self._big_endian:
791
i = reversed(i)
792
o = reversed(o)
793
l.append( (k(vector(i)), k(vector(o))) )
794
795
P = PolynomialRing(k,'x')
796
return P.lagrange_polynomial(l)
797
798
def cnf(self, xi=None, yi=None, format=None):
799
"""
800
Return a representation of this S-Box in conjunctive normal
801
form.
802
803
This function examines the truth tables for each output bit of
804
the S-Box and thus has complexity `n * 2^m` for an ``m x n``
805
S-Box.
806
807
INPUT:
808
809
- ``xi`` - indices for the input variables (default: ``1...m``)
810
811
- ``yi`` - indices for the output variables (default: ``m+1 ... m+n``)
812
813
- ``format`` - output format, see below (default: ``None``)
814
815
FORMATS:
816
817
- ``None`` - return a list of tuples of integers where each
818
tuple represents a clause, the absolute value of an integer
819
represents a variable and the sign of an integer indicates
820
inversion.
821
822
- ``symbolic`` - a string that can be parsed by the
823
``SymbolicLogic`` package.
824
825
- ``dimacs`` - a string in DIMACS format which is the gold
826
standard for SAT-solver input (cf. http://www.satlib.org/).
827
828
- ``dimacs_headless`` - a string in DIMACS format, but without
829
the header. This is useful for concatenation of outputs.
830
831
EXAMPLE:
832
833
We give a very small example to explain the output format::
834
835
sage: S = mq.SBox(1,2,0,3); S
836
(1, 2, 0, 3)
837
sage: cnf = S.cnf(); cnf
838
[(1, 2, -3), (1, 2, 4),
839
(1, -2, 3), (1, -2, -4),
840
(-1, 2, -3), (-1, 2, -4),
841
(-1, -2, 3), (-1, -2, 4)]
842
843
This output completely describes the S-Box. For instance, we
844
can check that ``S([0,1]) -> [1,0]`` satisfies every clause if
845
the first input bit corresponds to the index ``1`` and the
846
last output bit corresponds to the index ``3`` in the
847
output.
848
849
We can convert this representation to the DIMACS format::
850
851
sage: print S.cnf(format='dimacs')
852
p cnf 4 8
853
1 2 -3 0
854
1 2 4 0
855
1 -2 3 0
856
1 -2 -4 0
857
-1 2 -3 0
858
-1 2 -4 0
859
-1 -2 3 0
860
-1 -2 4 0
861
862
For concatenation we can strip the header::
863
864
sage: print S.cnf(format='dimacs_headless')
865
1 2 -3 0
866
1 2 4 0
867
1 -2 3 0
868
1 -2 -4 0
869
-1 2 -3 0
870
-1 2 -4 0
871
-1 -2 3 0
872
-1 -2 4 0
873
874
This might be helpful in combination with the ``xi`` and
875
``yi`` parameter to assign indices manually::
876
877
sage: print S.cnf(xi=[10,20],yi=[30,40], format='dimacs_headless')
878
10 20 -30 0
879
10 20 40 0
880
10 -20 30 0
881
10 -20 -40 0
882
-10 20 -30 0
883
-10 20 -40 0
884
-10 -20 30 0
885
-10 -20 40 0
886
887
We can also return a string which is parse-able by the
888
``SymbolicLogic`` package::
889
890
sage: log = SymbolicLogic()
891
sage: s = log.statement(S.cnf(format='symbolic'))
892
sage: log.truthtable(s)[1:]
893
[['False', 'False', 'False', 'False', 'False'],
894
['False', 'False', 'False', 'True', 'False'],
895
['False', 'False', 'True', 'False', 'False'],
896
['False', 'False', 'True', 'True', 'True'],
897
['False', 'True', 'False', 'False', 'True'],
898
['False', 'True', 'False', 'True', 'True'],
899
['False', 'True', 'True', 'False', 'True'],
900
['False', 'True', 'True', 'True', 'True'],
901
['True', 'False', 'False', 'False', 'True'],
902
['True', 'False', 'False', 'True', 'True'],
903
['True', 'False', 'True', 'False', 'True'],
904
['True', 'False', 'True', 'True', 'True'],
905
['True', 'True', 'False', 'False', 'True'],
906
['True', 'True', 'False', 'True', 'True'],
907
['True', 'True', 'True', 'False', 'True'],
908
['True', 'True', 'True', 'True', 'True']]
909
910
911
This function respects endianness of the S-Box::
912
913
sage: S = mq.SBox(1,2,0,3, big_endian=False); S
914
(1, 2, 0, 3)
915
sage: cnf = S.cnf(); cnf
916
[(1, 2, -4), (1, 2, 3),
917
(-1, 2, 4), (-1, 2, -3),
918
(1, -2, -4), (1, -2, -3),
919
(-1, -2, 4), (-1, -2, 3)]
920
921
S-Boxes with m!=n also work:
922
923
sage: o = range(8) + range(8)
924
sage: shuffle(o)
925
sage: S = mq.SBox(o)
926
sage: S.is_permutation()
927
False
928
929
sage: len(S.cnf()) == 3*2^4
930
True
931
932
933
TESTS:
934
935
sage: S = mq.SBox(1,2,0,3, big_endian=False)
936
sage: S.cnf([1000,1001,1002], [2000,2001,2002])
937
Traceback (most recent call last):
938
...
939
TypeError: first arg required to have length 2, got 3 instead.
940
"""
941
m, n = self.m, self.n
942
943
if xi is None:
944
xi = [i+1 for i in range(m)]
945
946
if yi is None:
947
yi = [m+i+1 for i in range(n)]
948
949
if len(xi) != m:
950
raise TypeError("first arg required to have length %d, got %d instead."%(m,len(xi)))
951
952
if len(yi) != n:
953
raise TypeError("second arg required to have length %d, got %d instead."%(n,len(yi)))
954
955
output_bits = range(n)
956
if not self._big_endian:
957
output_bits = list(reversed(output_bits))
958
959
C = [] # the set of clauses
960
for e in xrange(2**m):
961
x = self.to_bits(e, m)
962
y = self(x) # evaluate at x
963
for output_bit in output_bits: # consider each bit
964
clause = [(-1)**(int(v)) * i for v,i in zip(x, xi)]
965
clause.append( (-1)**(1-int(y[output_bit])) * yi[output_bit] )
966
C.append(tuple(clause))
967
968
if format is None:
969
return C
970
elif format == 'symbolic':
971
gd = self.ring().gens()
972
formula = []
973
for clause in C:
974
clause = "|".join([str(gd[abs(v)-1]).replace("-","~") for v in clause])
975
formula.append("("+clause+")")
976
return " & ".join(formula)
977
978
elif format.startswith('dimacs'):
979
if format == "dimacs_headless":
980
header = ""
981
else:
982
header = "p cnf %d %d\n"%(m+n,len(C))
983
values = " 0\n".join([" ".join(map(str,line)) for line in C])
984
return header + values + " 0\n"
985
else:
986
raise ValueError("Format '%s' not supported."%(format,))
987
988