Source code for sboxU.apn.database

from sboxU.core import \
    get_sbox, \
    degree_spectrum, algebraic_degree, quadratic_compact_representation, quadratic_sbox_from_compact_representation

from sboxU.statistics import \
    differential_spectrum, \
    walsh_spectrum, absolute_walsh_spectrum, linearity

from sboxU.ccz import \
    thickness_spectrum, \
    get_WalshZeroesSpaces, \
    ccz_equivalent_function, \
    are_ea_equivalent, \
    are_ccz_equivalent\
   

from sboxU.apn import \
    get_WalshZeroesSpaces_quadratic_apn, \
    sigma_multiplicities, \
    apn_ea_mugshot, apn_ea_mugshot_from_spectra, ccz_equivalent_quadratic_function, ea_mappings_from_ortho_derivative
    

from sboxU.databases import *
import hashlib

                 

[docs] class APNFunctions(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 provides another table containing the bases of all the spaces of dimension n contained with the Walsh zeroes of a function. In order to both save space and store the structure of a CCZ-equivalence, each APN function is stored along with the identifier of the Walsh spaces of its CCZ-equivalence class, and the FastLinearMapping that must be applied to it to obtain its own Walsh Zeroes. """ def __init__(self, db_file): # !IDEA! have a max_degree and a min_degree? super().__init__( db_file, { "lut" : "BLOB", # the lookup table of the representative "n" : "INTEGER", "m" : "INTEGER", "linearity" : "INTEGER", "thickness" : "INTEGER", "degree" : "INTEGER", "bibliography" : "TEXT", "ccz_id": "INTEGER", "mugshot" : "BLOB" } ) if self.new_db: self.number_of_ccz_classes = 0 else: try: self.cursor.execute("SELECT COUNT(ccz_id) FROM {}".format(self.functions_table)) self.number_of_ccz_classes = self.cursor.fetchall()[0][0] except Exception: self.number_of_ccz_classes = 0 def __str__(self): return "APN function DB containing {} EA-classes from {} CCZ-classes".format( self.number_of_functions, self.number_of_ccz_classes ) # def insert_new_ea_repr(self, s, bibliography): # sb = get_sbox(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(lut)) # # spectra # walsh_spec = walsh_spectrum(sb) # lin = walsh_spec.absolute().maximum() # degree_spec = degree_spectrum(sb) # deg = degree_spec.maximum() # thk_spec = thickness_spectrum(sb) # thk = thk_spec.maximum() # sig_spec = sigma_multiplicities(sb, k=4) # # we assume that it is from a # self.number_of_ccz_classes += 1 # if deg == 2: # mug = apn_ea_mugshot(sb) # else: # mug = apn_ea_mugshot_from_spectra(walsh_spec, # degree_spec, # sig_spec, # thk_spec) # # inserting the function # to_insert = { # "lut" : sb.to_bytes(), # "n" : sb.get_input_length(), # "m" : sb.get_output_length(), # "bibliography" : bibliography, # "linearity" : lin, # "degree" : deg, # "thickness" : thk, # "ccz_id" : self.number_of_ccz_classes, # "mugshot" : mug # } # return self.insert_function(to_insert)
[docs] def insert_full_ccz_equivalence_class(self, s, bibliography): sb = get_sbox(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: \nspec={}\ns={}".format(differential_spec, sb)) encoded = sb.to_bytes() # linear abs_walsh_spec = absolute_walsh_spectrum(sb) lin = abs_walsh_spec.maximum() inserted_ids = [] if algebraic_degree(sb) == 2: # if the function is quadratic, # we compute automorphisms # inserting the spaces ws = get_WalshZeroesSpaces_quadratic_apn(sb) quadratic = True else: ws = get_WalshZeroesSpaces(sb) quadratic = False # inserting all the functions for L in ws.get_mappings(): new_sb = ccz_equivalent_function(sb, L) new_L = L.transpose() new_L = new_L.inverse() new_ws = ws.image_by(new_L) new_thk_spec = new_ws.thickness_spectrum() new_degree_spec = degree_spectrum(new_sb) new_sigma_mult = sigma_multiplicities(new_sb, k=4) if quadratic: worth_adding = True else: worth_adding = self.is_new( new_sb, degree_spec=new_degree_spec, abs_walsh_spec=abs_walsh_spec, sigma_mult=new_sigma_mult, thk_spec=new_thk_spec, ccz_id=self.number_of_ccz_classes ) if worth_adding: if new_degree_spec.maximum() == 2: mug = apn_ea_mugshot(new_sb) else: mug = apn_ea_mugshot_from_spectra( abs_walsh_spec, new_degree_spec, new_sigma_mult, new_thk_spec ) to_insert = { "lut" : new_sb.to_bytes(), "n" : new_sb.get_input_length(), "m" : new_sb.get_output_length(), "bibliography" : bibliography, "linearity" : lin, "degree" : new_degree_spec.maximum(), "thickness" : new_thk_spec.maximum(), "ccz_id" : self.number_of_ccz_classes, "mugshot" : mug } inserted_ids.append(self.insert_function(to_insert)) self.number_of_ccz_classes += 1 return inserted_ids
[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"] = get_sbox(entry["lut"]) return entry
[docs] def is_new(self, s, degree_spec=None, abs_walsh_spec=None, sigma_mult=None, thk_spec=None, ccz_id=None ): """ # !TODO! docstring """ sb = get_sbox(s) if degree_spec == None: degree_spec = degree_spectrum(sb) if degree_spec.maximum() == 2: mug = apn_ea_mugshot(sb) else: if abs_walsh_spec == None: abs_walsh_spec = absolute_walsh_spectrum(sb) if sigma_mult == None: sigma_mult = sigma_multiplicities(sb) if thk_spec == None: thk_spec = thickness_spectrum(sb) mug = apn_ea_mugshot_from_spectra( abs_walsh_spec, degree_spec, sigma_mult, thk_spec ) query = {"mugshot" : mug} if ccz_id != None: query["ccz_id"] = ccz_id candidates = self.query_functions(query) if candidates == []: print("+ no candidate") return True else: # checking all the functions with a similar mugshot print("\n\n", len(candidates)) print(mug) print(sb) print("___") for entry in candidates: print(entry["id"], entry["ccz_id"]) print(entry["mugshot"]) if sb == entry["sbox"]: print("Identic to {}".format(entry["id"])) return False if are_ea_equivalent(sb.lut(), entry["sbox"].lut()): print("EA to entry {}".format(entry["id"])) print(entry["sbox"]) return False return True
[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", "linearity" : "INTEGER" } ) if self.new_db: self.number_of_ccz_classes = 0 else: try: self.cursor.execute("SELECT COUNT(id) FROM {}".format(self.functions_table)) self.number_of_ccz_classes = self.cursor.fetchall()[0][0] except Exception: 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 = get_sbox(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" : bytearray(quadratic_compact_representation(sb.lut())), "mugshot" : mug, "linearity" : linearity(sb.lut()) } 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"] = get_sbox(quadratic_sbox_from_compact_representation(entry["qcr"],8,8)) return entry
[docs] def is_new(self, s, mug=None ): """ # !TODO! docstring """ sb = get_sbox(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(get_sbox(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(qcr,sb.get_input_length(),sb.get_output_length()) mappings = ea_mappings_from_ortho_derivative(sb.lut(), lut) if mappings != []: print("Function EA equivalent to id = {} in the database".format(entry["id"])) return False return True
[docs] def insert_many_functions(self,entries): start_id = self.number_of_functions end_id = start_id to_insert = [] for e in entries: e["id"] = end_id end_id +=1 e_tuple = tuple([e[k] for k in sorted(self.row_structure.keys())]) to_insert.append(e_tuple) try: #self.cursor.executemany(self.function_insertion_query,to_insert) self.cursor.executemany( "INSERT INTO functions(id, linearity, mugshot, qcr) VALUES (?,?,?,?)",to_insert) self.number_of_functions = end_id return list(range(start_id,end_id+1)) except Exception as e: raise Exception("Insertion of many entries failed \n") from e