r"""
Bent Boolean functions
======================
The ``bent_function`` module defines
the ``BentFunction`` class,
which represents a bent Boolean function and some of its properties.
AUTHORS:
- Paul Leopardi (2016-09-25): initial version
EXAMPLES:
::
sage: from sage.crypto.boolean_function import BooleanFunction
sage: bf = BooleanFunction([0,1,0,0])
sage: bf.algebraic_normal_form()
x0*x1 + x0
sage: from boolean_cayley_graphs.bent_function import BentFunction
sage: bentf = BentFunction(bf)
sage: type(bentf)
<class 'boolean_cayley_graphs.bent_function.BentFunction'>
sage: bentf.algebraic_normal_form()
x0*x1 + x0
REFERENCES:
.. Dillon [Dil1974]_, Rothaus [Rot1976]_, Tokareva [Tok2015]_.
"""
#*****************************************************************************
# Copyright (C) 2016-2020 Paul Leopardi paul.leopardi@gmail.com
#
# Distributed under the terms of the GNU General Public License (GPL)
# as published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
# http://www.gnu.org/licenses/
#*****************************************************************************
from sage.graphs.graph import Graph
from sage.graphs.strongly_regular_db import strongly_regular_from_two_weight_code
from sage.misc.banner import require_version
from sage.matrix.constructor import matrix
from boolean_cayley_graphs.boolean_function_improved import BooleanFunctionImproved
import boolean_cayley_graphs.weight_class as wc
[docs]class BentFunction(BooleanFunctionImproved):
r"""
A bent Boolean function, with methods corresponding to some of its properties.
The class inherits from BooleanFunctionImproved and is initialized
in the same way as BooleanFunction.
Since BooleanFunctionImproved inherits from Saveable, so does BentFunction.
EXAMPLES:
::
sage: import os
sage: from boolean_cayley_graphs.bent_function import BentFunction
sage: bentf = BentFunction([0,0,0,1])
sage: bentf.algebraic_normal_form()
x0*x1
sage: d = tmp_dir()
sage: bentf.save_mangled('example', dir=d)
sage: ex = BentFunction.load_mangled('example', dir=d)
sage: type(ex)
<class 'boolean_cayley_graphs.bent_function.BentFunction'>
sage: ex is bentf
False
sage: ex == bentf
True
sage: BentFunction.remove_mangled('example', dir=d)
sage: os.rmdir(d)
TESTS:
::
sage: from sage.crypto.boolean_function import BooleanFunction
sage: bf = BentFunction([0,1,0,0])
sage: print(bf)
Boolean function with 2 variables
sage: from sage.crypto.boolean_function import BooleanFunction
sage: bf = BentFunction([0,1,0,0])
sage: latex(bf)
\text{\texttt{Boolean{ }function{ }with{ }2{ }variables}}
"""
[docs] def is_partial_spread_minus(self, certify=False):
r"""
Determine if a bent function is in the partial spread (-) class.
Partial spread (-) is Dillon's :math:`\mathcal{PS}^{(-)}` class [Dil1974]_.
INPUT:
- ``self`` -- the current object.
- ``certify`` -- boolean (default: False):
OUTPUT:
If certify is False, then return True if the bent function
represented by ``self`` is in :math:`\mathcal{PS}^{(-)}`,
otherwise return False.
If certify is True then return two values,
where the first is True or False as above,
and if the first value is True, the second value is
a list of intersections between maximal cliques,
otherwie the second value is an empty list.
EXAMPLES:
::
sage: from boolean_cayley_graphs.bent_function import BentFunction
sage: bentf = BentFunction([0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,0])
sage: bentf.is_bent()
True
sage: bentf.is_partial_spread_minus()
True
sage: bentf.is_partial_spread_minus(certify=True)
(True, [{7, 11, 12}, {3, 13, 14}])
"""
dim = self.nvariables()
reqd_clique_size = 2 ** (dim // 2)
cayley = self.cayley_graph()
cliques_0 = cayley.cliques_containing_vertex(0)
reqd_cliques = [
set(clique)
for clique in cliques_0
if (len(clique) >= reqd_clique_size)]
nbr_cliques = len(reqd_cliques)
if nbr_cliques == 0:
if certify:
return False, []
else:
return False
clique_matrix = matrix(nbr_cliques)
for j in range(nbr_cliques):
for k in range(j + 1, nbr_cliques):
clique_matrix[j, k] = clique_matrix[k, j] = (
1
if reqd_cliques[j] & reqd_cliques[k] == {0} else
0)
clique_graph = Graph(clique_matrix)
clique_max = clique_graph.clique_maximum()
if len(clique_max) == reqd_clique_size // 2:
if certify:
part = [
reqd_cliques[j] - {0}
for j in clique_max]
return True, part
else:
return True
else:
if certify:
intersections = []
for j in range(nbr_cliques):
for k in range(j + 1, nbr_cliques):
intersections.append(
((j,k), reqd_cliques[j] & reqd_cliques[k]))
return False, intersections
else:
return False
[docs] def linear_code_graph(self):
r"""
Return the graph of the linear code corresponding to the bent function.
INPUT:
- ``self`` -- the current object.
OUTPUT:
The graph of the linear code corresponding to ``self``.
This is a strongly regular graph [Car2010]_, [Del1972]_, [Din2015].
EXAMPLES:
::
sage: from boolean_cayley_graphs.bent_function import BentFunction
sage: bentf = BentFunction([0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,0])
sage: lcg = bentf.linear_code_graph()
sage: type(lcg)
<class 'sage.graphs.graph.Graph'>
sage: lcg.is_strongly_regular(parameters=True)
(16, 6, 2, 2)
REFERENCES:
.. Carlet [Car2010]_ Section 8.6 Proposition 8.29.
.. Delsarte [Del1972]_.
.. Ding [Din2015]_ Corollary 10.
"""
L = self.linear_code()
return strongly_regular_from_two_weight_code(L)
[docs] def sdp_design_matrix(self):
r"""
Return the incidence matrix of the SDP design of the bent function.
This method returns the incidence matrix of the design of type
:math:`R(\mathtt{self})`, as described by Dillon and Schatz [DS1987]_.
This is a design with the symmetric difference property [Kan1975]_.
INPUT:
- ``self`` -- the current object.
OUTPUT:
The incidence matrix of the SDP design corresponding to ``self``.
EXAMPLES:
::
sage: from boolean_cayley_graphs.bent_function import BentFunction
sage: bentf = BentFunction([0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,0])
sage: sdp = bentf.sdp_design_matrix()
sage: print(sdp)
[0 0 0 1 0 0 0 1 0 0 0 1 1 1 1 0]
[0 1 0 0 0 1 0 0 0 1 0 0 1 0 1 1]
[0 0 1 0 0 0 1 0 0 0 1 0 1 1 0 1]
[1 0 0 0 1 0 0 0 1 0 0 0 0 1 1 1]
[0 0 0 1 1 1 1 0 0 0 0 1 0 0 0 1]
[0 1 0 0 1 0 1 1 0 1 0 0 0 1 0 0]
[0 0 1 0 1 1 0 1 0 0 1 0 0 0 1 0]
[1 0 0 0 0 1 1 1 1 0 0 0 1 0 0 0]
[0 0 0 1 0 0 0 1 1 1 1 0 0 0 0 1]
[0 1 0 0 0 1 0 0 1 0 1 1 0 1 0 0]
[0 0 1 0 0 0 1 0 1 1 0 1 0 0 1 0]
[1 0 0 0 1 0 0 0 0 1 1 1 1 0 0 0]
[1 1 1 0 0 0 0 1 0 0 0 1 0 0 0 1]
[1 0 1 1 0 1 0 0 0 1 0 0 0 1 0 0]
[1 1 0 1 0 0 1 0 0 0 1 0 0 0 1 0]
[0 1 1 1 1 0 0 0 1 0 0 0 1 0 0 0]
sage: from sage.combinat.designs.incidence_structures import IncidenceStructure
sage: sdp_design = IncidenceStructure(sdp)
sage: sdp_design.is_t_design(return_parameters=True)
(True, (2, 16, 6, 2))
REFERENCES:
.. Dillon and Schatz [DS1987]_, Kantor [Kan1975]_.
"""
dim = self.nvariables()
v = 2 ** dim
result = matrix(v, v)
dual_self = self.walsh_hadamard_dual()
dual_f = dual_self.extended_translate()
for c in range(v):
result[c,:] = matrix([self.extended_translate(0, c, dual_f(c))(x)
for x in range(v)])
return result
[docs] def walsh_hadamard_dual(self):
r"""
Return the Walsh Hadamard dual of the bent function.
This method returns a ``BentFunction`` based on the Walsh Hadamard
transform of ``self``. Since ``self`` is a bent function, the returned
``BentFunction` is well-defined and is bent, being the *dual*
bent function [Hou1999]_ or *Fourier transform* of ``self`` [Rot1976]_.
INPUT:
- ``self`` -- the current object.
OUTPUT:
An object of class ``BentFunction``, being the dual of ``self``.
EXAMPLES:
::
sage: from boolean_cayley_graphs.bent_function import BentFunction
sage: bentf = BentFunction([0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,0])
sage: dual_bentf = bentf.walsh_hadamard_dual()
sage: type(dual_bentf)
<class 'boolean_cayley_graphs.bent_function.BentFunction'>
sage: dual_bentf.is_bent()
True
sage: dual_bentf.algebraic_normal_form()
x0*x1 + x2*x3
.. NOTE::
Versions of Sage before 8.2 had an incorrect sign in
``BooleanFunction.walsh_hadamard_transform(self)``.
See [Sage trac ticket #23931](https://trac.sagemath.org/ticket/23931)
Previous versions of this method had ``1 + x // scale`` to compensate for
an incorrect sign in ``BooleanFunction.walsh_hadamard_transform(self)``.
[Since this has now been fixed in Sage 8.2](https://trac.sagemath.org/ticket/23931)
this is now changed to ``1 - x // scale``.
"""
dim = self.nvariables()
scale = 2 ** (dim // 2)
coefficient = lambda x: (
(1 - x // scale) // 2
if require_version(8,2,0)
else
(1 + x // scale) // 2)
return BentFunction([coefficient(x) for x in self.walsh_hadamard_transform()])
[docs] def weight_class(self):
r"""
Return the weight class of the bent function.
INPUT:
- ``self`` -- the current object.
OUTPUT:
The value 0 or 1, being the weight class of ``self``.
EXAMPLES:
::
sage: from boolean_cayley_graphs.bent_function import BentFunction
sage: bentf = BentFunction([0,0,0,1,0,0,0,1,0,0,0,1,1,1,1,0])
sage: bentf.weight_class()
0
"""
length = len(self)
weight = self.weight()
return wc.weight_class(length, weight)