from sboxUv2 import *
from pathlib import Path
import argparse
import hashlib
########################################################
#### !SECTION! Code For Generic EA Classes Database ####
########################################################
[docs]
def generate_apn_ea_classes_database(
ccz_class_representatives,
db_path
):
"""!TODO! write docstring
"""
with Experiment("Generating database"):
section("Grabbing class representatives")
subsection("reading")
# generating the DB
print("path = ", db_path)
if Path(db_path).is_file():
print("[WARNING] file already exists, we are deleting it")
Path(db_path).unlink()
else:
print("DB will be stored in {}".format(db_path))
print("using {} class representatives".format(len(ccz_class_representatives)))
subsection("splitting into quadratic and non-quadratic")
quads = []
non_quads = []
for s in ccz_class_representatives:
if algebraic_degree(s) == 2:
quads.append(s)
else:
non_quads.append(s)
with APNFunctions(db_path) as db:
section("filling the database with CCZ-quadratic EA classes")
for index, s in enumerate(quads):
inserted = db.insert_full_ccz_equivalence_class(
s,
"6-bit"
)
print("{:5d}) CCZ class has {} functions".format(index, len(inserted)))
section("filling the database with non-CCZ-quadratic EA classes")
for s in non_quads:
pprint(thickness_spectrum(s))
pprint(degree_spectrum(s))
inserted = db.insert_full_ccz_equivalence_class(
s,
"--"
)
print("CCZ class with {} functions added".format(len(inserted)))
print("generation finished")
print("{} EA classes generated".format(len(db)))
print("{} CCZ classes found".format(db.number_of_ccz_classes))
section("How to use it?")
print("\n1. Put the file {} in the folder with your script".format(db_path))
print("\n2. make sure you import sboxUv2 in your sage script")
print("\n3. use the following syntax to check if F is new:")
print("with APNFunctions('{}') as db:".format(db_path))
print(" print('Yay!' if db.is_new(F) else ':((')")
print("\n4. use the following to print the Walsh spectrum of all degree 3 functions:")
print("with APNFunctions('{}') as db:".format(db_path))
print(" for entry in db.query_functions({'degree' : 3}):")
print(" s = entry['sbox']")
print(" pprint(walsh_spectrum(s))")
print("\n\n")
[docs]
def process_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--path",
nargs="?",
type=str,
default=DEFAULT_APN_DB_NAME,
help="The path to the file in which to write the database")
parser.add_argument("-n",
type=int,
help="The value of the bit length of the functions to process")
return parser.parse_args()
################################################################
#### !SECTION! Code For CCZ only quadratic compact database ####
################################################################
[docs]
def are_ea_equivalent_from_vq(f,g):
# !! TODO !!
# Add base cases
ws_f = get_WalshZeroesSpaces(f)
mappings_f = list(ws_f.get_mappings())
ws_g = get_WalshZeroesSpaces(g)
mappings_g = list(ws_g.get_mappings())
quad_index_f = 0
for i in range(len(mappings_f)):
f_eq = ccz_equivalent_function(f, mappings_f[i])
if algebraic_degree(f_eq) == 2:
quad_index_f = i
break
quad_index_g = 0
for i in range(len(mappings_g)):
g_eq = ccz_equivalent_function(g, mappings_g[i])
if algebraic_degree(g_eq) == 2:
quad_index_g = i
break
# Compute of the ccz-quadratic aut(q)
q = ccz_equivalent_quadratic_function(f)
aut_q = automorphisms_from_ortho_derivative(q)
# Compute the EA mapping
q_prime = ccz_equivalent_quadratic_function(g)
ea_mappings = ea_mappings_from_ortho_derivative(q_prime,q)
if len(ea_mappings) != 0 :
ea_q_q_prime = ea_mappings[0]
else:
return(False)
# To check
#print(q_prime == ccz_equivalent_function(q,ea_q_q_prime))
ws_1 = ws_f.image_by(mappings_f[quad_index_f].inverse().transpose())
ws_1 = ws_1.image_by(mappings_f[quad_index_f].inverse().transpose())
ws_2 = ws_g.image_by(mappings_g[quad_index_g].inverse().transpose())
ws_2 = ws_2.image_by(mappings_g[quad_index_g].inverse().transpose())
ws_2 = ws_2.image_by(ea_q_q_prime.transpose())
#print(ws_1.get_bases()[quad_index_f])
#print(ws_2.get_bases()[quad_index_g])
for L in aut_q:
ws_temp = ws_1.image_by(L.transpose().inverse())
if ws_2.get_bases()[quad_index_g] == ws_temp.get_bases()[quad_index_f]:
return(True)
return(False)
DEFAULT_APN_DB_NAME = "apnDB.db"
[docs]
class APNQuadraticFunctions_ccz_only(FunctionsDB):
"""This class is expected to be bundled with a literal TinySQL
database file called "apn_functions.db", and allows an easy
interaction with it.
It builds upon the `FunctionDB` class, and contains additional
logic to handle the specifics of APN functions, and in particular
of their CCZ-equivalence class. Here, "functions" should be
thought of much more as "extended affine equivalence class
representative" rather than function.
This class is a compact version of APNFunctions to use when there are
too many functions when we include EA-classes.
"""
def __init__(self, db_file):
# !IDEA! have a max_degree and a min_degree?
super().__init__(
db_file,
{
"qcr" : "BLOB",
"mugshot" : "BLOB"
}
)
if self.new_db:
self.number_of_ccz_classes = 0
else:
try:
self.cursor.execute("SELECT COUNT(id) FROM {}".format(self.functions_table))
except:
self.number_of_ccz_classes = 0
def __str__(self):
return "APN function DB containing {} CCZ-classes".format(
self.number_of_functions
)
[docs]
def insert_quadratic_ccz_representative(self, s):
"""
In this version, we only insert a ccz representative
"""
sb = Sb(s)
differential_spec = differential_spectrum(sb)
if differential_spec.maximum() != 2:
raise Exception("Trying to add a non-APN function to the APN function database: {}".format(sb))
if algebraic_degree(sb) == 2:
mug = apn_ea_mugshot(sb)
# We hash the mugshot for memory concerns
h = hashlib.sha256()
h.update(mug)
mug = h.digest()
worth_adding = self.is_new(sb,mug)
else:
s_quad = ccz_equivalent_quadratic_function(sb.lut())
if s_quad != []:
sb = s_quad
mug = apn_ea_mugshot(sb)
# We hash the mugshot for memory concerns
h = hashlib.sha256()
h.update(mug)
mug = h.digest()
worth_adding = self.is_new(sb,mug)
else:
raise Exception("Trying to add a non ccz_quadratic APN function to the database: {}".format(sb))
if worth_adding:
to_insert = {
"qcr" : Sb(quadratic_compact_representation(sb.lut())).to_bytes(),
"mugshot" : mug
}
inserted_id = self.insert_function(to_insert)
self.number_of_ccz_classes += 1
return inserted_id
return None
[docs]
def parse_function_from_row(self, row):
entry = {}
for i, column in enumerate(sorted(self.row_structure.keys())):
entry[column] = row[i]
# post-processing
entry["sbox"] = Sb(quadratic_sbox_from_compact_representation(entry["qcr"],8,8))
return entry
[docs]
def is_new(self,
s,
mug=None
):
"""
# !TODO! docstring
"""
sb = Sb(s)
if mug == None:
# Computing the mugshot of s depending on its degree
if degree_spec == None:
degree_spec = degree_spectrum(sb)
if degree_spec.maximum() == 2:
mug = apn_ea_mugshot(sb)
else:
# If s is ccz quadratic, we work with a quadratic representative
s_quad = ccz_equivalent_quadratic_function(sb)
if s_quad == []:
# !! TODO !!
# Decide if we put more
mug = absolute_walsh_spectrum(sb)
else:
mug = apn_ea_mugshot(Sb(s_quad))
# We hash the mugshot for memory concerns
h = hashlib.sha256()
h.update(mug)
mug = h.digest()
query = {"mugshot" : mug}
candidates = self.query_functions(query)
if candidates == []:
#print("- New Mugshot")
return True
else:
for entry in candidates:
print("Same Mugshot as Function {}".format(entry["id"]))
print(entry["mugshot"])
# Trivial Case
if quadratic_compact_representation(sb.lut()) == entry["qcr"]:
return False
# Testing EA-equivalence
qcr = entry["qcr"]
lut = quadratic_sbox_from_compact_representation(Sb(qcr).lut(),sb.get_input_length(),sb.get_output_length())
b = are_ea_equivalent_from_vq(sb.lut(), lut)
if b:
print("Function EA equivalent to id = {} in the database".format(entry["id"]))
return False
return True
[docs]
def generate_apn_ccz_classes_database(
ccz_class_representatives,
db_path
):
"""!TODO! write docstring
"""
with Experiment("Generating database"):
section("Grabbing class representatives")
subsection("reading")
# Generating the DB
print("path = ", db_path)
if Path(db_path).is_file():
print("[WARNING] file already exists, we are deleting it")
Path(db_path).unlink()
else:
print("DB will be stored in {}".format(db_path))
print("using {} class representatives".format(len(ccz_class_representatives)))
with APNQuadraticFunctions_ccz_only(db_path) as db:
section("filling the database with CCZ-quadratic classes")
for index, s in enumerate(ccz_class_representatives):
#print("Trying {}th Function in Representative List".format(index))
if algebraic_degree(s) == 2:
inserted = db.insert_quadratic_ccz_representative(s)
if inserted == None :
print("- {}th Function is not New".format(index))
print()
#else:
# print("- {}th Function is New".format(index))
# print()
else:
print("Non Quadratic Function, discarded")
print()
print("generation finished")
print("{} CCZ classes found".format(db.number_of_ccz_classes))
[docs]
def main_cli():
args = process_arguments()
n = args.n
path = args.path
mode = 'ea'
#mode = 'ccz_compact'
print(n, path,mode)
if n == 6:
from .reprs6 import ccz_class_representatives
elif n == 7:
from .reprs7 import ccz_class_representatives
elif n == 8:
from .reprs8 import ccz_class_representatives
if n not in [6,7,8]:
print("n must be 6,7 or 8")
else:
if mode == 'ccz_compact':
generate_apn_ccz_classes_database(
ccz_class_representatives,
path
)
else:
generate_apn_ea_classes_database(
ccz_class_representatives,
path
)
if __name__ == "__main__":
main_cli()