Browse Source

Clean 'SimulationProject' + Add docstrings

master
Thibauld Feneuil 4 years ago
parent
commit
443abe51a5
7 changed files with 149 additions and 31 deletions
  1. 17
    1
      README.md
  2. 19
    7
      elmo/engine.py
  3. 6
    0
      elmo/executor.py
  4. 75
    23
      elmo/project_base.py
  5. 1
    0
      elmo/templates/project.c
  6. 19
    0
      elmo/utils.py
  7. 12
    0
      test.py

+ 17
- 1
README.md View File

- The file _projectclass.py_ where there is the class of the simulation which will enable you to generate traces of the project in Python scripts; - The file _projectclass.py_ where there is the class of the simulation which will enable you to generate traces of the project in Python scripts;
- A _Makefile_ ready to be used with a compiler _arm-none-eabi-gcc_. - A _Makefile_ ready to be used with a compiler _arm-none-eabi-gcc_.
Usually a leaking code runs challenges, one challenge giving a power trace. A challenge is the execution of a code with a specific set of data. This set of data is given in the input of the leakage simulation. For example, one can imagine that the leaking code is a symmetric encryption and one wants to study its power leakage according to the message and the secret key. Then, a challenge is the simulation of the leakage for a specific message and for a specific secret key.
So, the classical form of _project.c_ is the following one:
- It gets a number of challenges with ```readbyte```.
- Then, it loops for each challenge.
- For the challenge, load the specific set of data with ```readbyte```.
- Start the record of the power leakage (start a power trace)
- Realise the leaking operations with the loaded set of data
- Stop the record of the power leakage (end a power trace)
- Eventually output some data with ```printbyte```
- Indicate to ELMO tool that the simulation is finished
The file _projectclass.py_ contains a subclass of ```SimulationProject```. It contains the description of the _project.c_ file for the ELMO tool, in order to correctly realise the simulation. The classmethod ```get_binary_path(cl)``` must return the relative path of the leakage binary (_project.c_ correctly compiled). The method ```set_input_for_each_challenge(self, input, challenge)``` must write a ```challenge``` in ```input``` using the function ```write```. Many methods of ```SimulationProject``` can be rewritten in the subclass if necessary. For example, in the case where your _project.c_ doesn't run challenges, you can rewrite the method ```set_input(self, input)```.
Important! Don't forget that _ELMO_ (and so _Python-ELMO_) needs a **compiled** version of _project.c_ (see the "Requirements" section for more details). The provided _Makefile_ is here to help you to compile.
### List all the available simulation ### List all the available simulation
```python ```python
Sometimes, it is impossible to run the simulation thanks the simple method ```run``` of the project class. Indeed, sometimes the Python script is executed in the environment where _Python-ELMO_ cannot launch the ELMO tool. For example, it is the case where _Python-ELMO_ is used in SageMath on Windows. On Windows, SageMath installation relies on the Cygwin POSIX emulation system and it can be a problem. Sometimes, it is impossible to run the simulation thanks the simple method ```run``` of the project class. Indeed, sometimes the Python script is executed in the environment where _Python-ELMO_ cannot launch the ELMO tool. For example, it is the case where _Python-ELMO_ is used in SageMath on Windows. On Windows, SageMath installation relies on the Cygwin POSIX emulation system and it can be a problem.
To offer a solution, _Python-ELMO_ can be used thanks to a client-server link. The idea is you must launch the script ```run_server.py``` which will listen (by default) at port 5000 in localhost.
To offer a solution, _Python-ELMO_ can be used thanks to a client-server link. The idea is you must launch the following script which will listen (by default) at port 5000 in localhost.
```bash ```bash
python -m elmo run-server python -m elmo run-server

+ 19
- 7
elmo/engine.py View File

SUBSEQUENT = 2 SUBSEQUENT = 2
class ELMOEngine: class ELMOEngine:
### Initialization
def __init__(self): def __init__(self):
self.load_coefficients() self.load_coefficients()
self.reset_points() self.reset_points()
return coeffs return coeffs
def load_coefficients(self): def load_coefficients(self):
""" Load the coefficients for the ELMO model about power leakage """
filename = os.path.join( filename = os.path.join(
os.path.dirname(os.path.abspath(__file__)), os.path.dirname(os.path.abspath(__file__)),
ELMO_TOOL_REPOSITORY, ELMO_TOOL_REPOSITORY,
self.BitFlip1_bitinteractions = self._extract_data(496) self.BitFlip1_bitinteractions = self._extract_data(496)
self.BitFlip2_bitinteractions = self._extract_data(496) self.BitFlip2_bitinteractions = self._extract_data(496)
def reset_points(self):
self.points = []
self.power = None
def add_point(self, triplet, previous_ops, current_ops):
self.points.append((triplet, previous_ops, current_ops))
### Computation core
def _dot(self, a, b): def _dot(self, a, b):
return np.sum(a * b, axis=0) return np.sum(a * b, axis=0)
BitFlip1_bitinteractions_data, BitFlip2_bitinteractions_data]) BitFlip1_bitinteractions_data, BitFlip2_bitinteractions_data])
return power return power
### To manage studied points
def reset_points(self):
""" Reset all the points previously added """
self.points = []
self.power = None
def add_point(self, triplet, previous_ops, current_ops):
""" Add a new point to analyse """
self.points.append((triplet, previous_ops, current_ops))
def run(self): def run(self):
""" Compute the power leakage of all the points previously added
Store the results in 'self.power'
"""
nb_points = len(self.points) nb_points = len(self.points)
triplet = np.array([p[0] for p in self.points]).T # shape = (3, nb_points) triplet = np.array([p[0] for p in self.points]).T # shape = (3, nb_points)
previous_ops = np.array([p[1] for p in self.points]).T # shape = (2, nb_points) previous_ops = np.array([p[1] for p in self.points]).T # shape = (2, nb_points)
self.power = self.calculate_point(triplet, previous_ops, current_ops) self.power = self.calculate_point(triplet, previous_ops, current_ops)
def oneshot_point(self, triplet, previous_ops, current_ops): def oneshot_point(self, triplet, previous_ops, current_ops):
""" Compute the power of a single point
defined by 'triplet', 'previous_ops', and 'current_ops'
"""
self.reset_points() self.reset_points()
self.add_point(triplet, previous_ops, current_ops) self.add_point(triplet, previous_ops, current_ops)
self.run() self.run()

+ 6
- 0
elmo/executor.py View File

class Executor(OneShotServiceThread): class Executor(OneShotServiceThread):
def execute(self): def execute(self):
""" Answer a request of simulation """
# Get simulation data
data = self.protocol.get_data() data = self.protocol.get_data()
self.protocol.please_assert(data) self.protocol.please_assert(data)
def launch_executor(host=DEFAULT_HOST, port=DEFAULT_PORT, waiting_function=True): def launch_executor(host=DEFAULT_HOST, port=DEFAULT_PORT, waiting_function=True):
""" Launch ELMO server on 'host' listening to the 'port' """
from .server.servicethread import ListeningThread from .server.servicethread import ListeningThread
def do_main_program(): def do_main_program():
def program_cleanup(signum, frame): def program_cleanup(signum, frame):
thread.stop() thread.stop()
# Launch the listening server
thread = do_main_program() thread = do_main_program()
# Give a way to stop the server
import signal import signal
signal.signal(signal.SIGINT, program_cleanup) signal.signal(signal.SIGINT, program_cleanup)
signal.signal(signal.SIGTERM, program_cleanup) signal.signal(signal.SIGTERM, program_cleanup)
# Do somthing during the execution of the server
if waiting_function is True: if waiting_function is True:
import time import time
while thread.is_running(): while thread.is_running():

+ 75
- 23
elmo/project_base.py View File

### Define the project ### Define the project
@classmethod @classmethod
def get_project_directory(cl): def get_project_directory(cl):
""" """
""" Return the project directory of the simulation """
if cl._project_directory: if cl._project_directory:
return cl._project_directory return cl._project_directory
else: else:
@classmethod @classmethod
def set_project_directory(cl, project_directory): def set_project_directory(cl, project_directory):
""" Set the project directory of the simulation """
cl._project_directory = project_directory cl._project_directory = project_directory
@classmethod @classmethod
def get_project_label(cl):
return cl.get_project_directory()
@classmethod
def get_make_directory(cl):
return ''
@classmethod
def get_binary_path(cl): def get_binary_path(cl):
""" Return the path of the leaking binary """
raise NotImplementedError() raise NotImplementedError()
@classmethod
def get_parameters_names(cl):
return set()
def get_challenge_format(self): def get_challenge_format(self):
""" Return the format of one challenge
Used by 'set_input_for_each_challenge' if not rewritten
"""
raise NotImplementedError() raise NotImplementedError()
### Tools to realize the simulation of the project ### Tools to realize the simulation of the project
def __init__(self, challenges=None): def __init__(self, challenges=None):
""" Initialize a simulation project
:challenge: The list of challenge for the simulation
"""
self.elmo_folder = pjoin(MODULE_PATH, ELMO_TOOL_REPOSITORY) self.elmo_folder = pjoin(MODULE_PATH, ELMO_TOOL_REPOSITORY)
self.challenges = challenges self.challenges = challenges
self.reset() self.reset()
def reset(self): def reset(self):
""" Reset the last simulation """
self.is_executed = False self.is_executed = False
self.has_been_online = False self.has_been_online = False
self._complete_results = None self._complete_results = None
self._complete_printed_data = None self._complete_printed_data = None
def get_number_of_challenges(self):
""" Return the number of challenge """
return len(self.challenges) if self.challenges else 0
def get_test_challenges(self): def get_test_challenges(self):
""" Return a fixed list of challenges for test """
raise NotImplementedError() raise NotImplementedError()
def get_random_challenges(self, nb_challenges): def get_random_challenges(self, nb_challenges):
""" Return a list of random challenges
:nb_challenges: Length of the list
"""
raise NotImplementedError() raise NotImplementedError()
def set_challenges(self, challenges): def set_challenges(self, challenges):
""" Reset the simulation and set the challenges of the next simulation
:challenges: The list of the challenges
"""
self.reset() self.reset()
self.challenges = challenges self.challenges = challenges
def get_input_filename(self): def get_input_filename(self):
""" Return (string) the path of the input file
of the local installation of ELMO tool
"""
return pjoin(self.elmo_folder, ELMO_INPUT_FILE_NAME) return pjoin(self.elmo_folder, ELMO_INPUT_FILE_NAME)
def get_printed_data_filename(self): def get_printed_data_filename(self):
""" Return the path (string) of the file containing the printed data
of the local installation of ELMO tool
"""
return pjoin(self.elmo_folder, 'output', 'printdata.txt') return pjoin(self.elmo_folder, 'output', 'printdata.txt')
def get_asmtrace_filename(self): def get_asmtrace_filename(self):
""" Return the path (string) of the file containing the ASM trace
of the local installation of ELMO tool
"""
return pjoin(self.elmo_folder, 'output', 'asmoutput', 'asmtrace00001.txt') return pjoin(self.elmo_folder, 'output', 'asmoutput', 'asmtrace00001.txt')
def set_input_for_each_challenge(self, input, challenge): def set_input_for_each_challenge(self, input, challenge):
""" Set the input for one challenge for a simulation with ELMO tool
:input: Descriptor of the input of the ELMO tool (write only)
:challenge: A challenge for the simulation
"""
format = self.get_challenge_format() format = self.get_challenge_format()
def aux(sizes, data): def aux(sizes, data):
aux(format[num_part], challenge[num_part]) aux(format[num_part], challenge[num_part])
def set_input(self, input): def set_input(self, input):
""" Set the input for a simulation with ELMO tool
First, it writes the number of challenges.
Then, it writes each challenge one by one thanks to the method 'set_input_for_each_challenge'
:input: Descriptor of the input of the ELMO tool (write only)
"""
if self.challenges: if self.challenges:
assert len(self.challenges) < (1 << self._nb_bits_for_nb_challenges), \
nb_challenges = self.get_number_of_challenges()
assert nb_challenges < (1 << self._nb_bits_for_nb_challenges), \
'The number of challenges must be strictly lower than {}. Currently, there are {} challenges.'.format( 'The number of challenges must be strictly lower than {}. Currently, there are {} challenges.'.format(
1 << self._nb_bits_for_nb_challenges, 1 << self._nb_bits_for_nb_challenges,
len(self.challenges),
nb_challenges,
) )
write(input, len(self.challenges), nb_bits=self._nb_bits_for_nb_challenges)
write(input, nb_challenges, nb_bits=self._nb_bits_for_nb_challenges)
for challenge in self.challenges: for challenge in self.challenges:
self.set_input_for_each_challenge(input, challenge) self.set_input_for_each_challenge(input, challenge)
def run(self): def run(self):
""" Run the simulation thanks the local installation of ELMO tool.
Using the leaking binary defined thanks to the method 'get_binary_path',
it will run the ELMO tool to output the leaked power traces.
The results of the simulation are available via the methods:
'get_results', 'get_traces', 'get_asmtrace' and 'get_printed_data'
Return the raw output of the compiled ELMO tool.
"""
self.reset() self.reset()
with open(self.get_input_filename(), 'w') as _input: with open(self.get_input_filename(), 'w') as _input:
self.set_input(_input) self.set_input(_input)
return res return res
def run_online(self, host=DEFAULT_HOST, port=DEFAULT_PORT): def run_online(self, host=DEFAULT_HOST, port=DEFAULT_PORT):
""" Run the simulation thanks to an ELMO server.
An ELMO server can be launched thanks to the command
>>> python -m elmo run-server 'host' 'port'
Using the leaking binary defined thanks to the method 'get_binary_path',
it will run the ELMO tool to output the leaked power traces.
The results of the simulation are available via the methods:
'get_results', 'get_traces', 'get_asmtrace' and 'get_printed_data'
Return the raw output of the compiled ELMO tool.
:host: The host of the ELMO server
:post! The port where the ELMO server is currently listening
"""
from .server.protocol import SocketTool from .server.protocol import SocketTool
import socket import socket
} }
### Manipulate the results ### Manipulate the results
def get_number_of_challenges(self):
return len(self.challenges)
def get_number_of_traces(self): def get_number_of_traces(self):
""" Get the number of traces of the last simulation """
assert self.is_executed assert self.is_executed
return self._nb_traces return self._nb_traces
def get_results_filenames(self): def get_results_filenames(self):
""" Get the filenames of the results of the last simulation
Return a list of filenames (strings), each file containing a power trace
"""
assert self.is_executed assert self.is_executed
assert not self.has_been_online assert not self.has_been_online
nb_traces = self.get_number_of_traces() nb_traces = self.get_number_of_traces()
return filenames return filenames
def get_results(self): def get_results(self):
"""
""" Get the raw outputs of the last simulation
Return a list of power traces (represented by a list of floats)
Warning: The output list is the same object stored in the instance. Warning: The output list is the same object stored in the instance.
If you change this object, it will change in the instance too, and the If you change this object, it will change in the instance too, and the
next call to 'get_results' will return the changed object. next call to 'get_results' will return the changed object.
return self._complete_results return self._complete_results
def get_traces(self, indexes=None): def get_traces(self, indexes=None):
"""
""" Get the power trace of the last simulation
Return a 2-dimensional numpy array of floats.
1st dimension: number of the trace
2nd dimension: power point of the trace
:indexes: if not None, return only the power points contained in :indexes:
Must be a list of indexes of power points.
""" """
assert self.is_executed assert self.is_executed
results = self.get_results() results = self.get_results()
else: else:
traces = np.zeros((nb_traces, len(indexes))) traces = np.zeros((nb_traces, len(indexes)))
for i in range(nb_traces): for i in range(nb_traces):
traces[i,:] = results[i][indexes]
traces[i,:] = np.array(results[i])[indexes]
return traces return traces
### Manipulate the ASM trace ### Manipulate the ASM trace
if self._complete_asmtrace is None: if self._complete_asmtrace is None:
with open(self.get_asmtrace_filename(), 'r') as _file: with open(self.get_asmtrace_filename(), 'r') as _file:
self._complete_asmtrace = _file.read() self._complete_asmtrace = _file.read()
if type(self._complete_asmtrace):
if type(self._complete_asmtrace) is not list:
self._complete_asmtrace = self._complete_asmtrace.split('\n') self._complete_asmtrace = self._complete_asmtrace.split('\n')
return self._complete_asmtrace return self._complete_asmtrace

+ 1
- 0
elmo/templates/project.c View File



read2bytes(&nb_challenges); read2bytes(&nb_challenges);
for(num_challenge=0; num_challenge<nb_challenges; num_challenge++) { for(num_challenge=0; num_challenge<nb_challenges; num_challenge++) {
// Set variables for the current challenge


starttrigger(); // To start a new trace starttrigger(); // To start a new trace
// Do the leaking operations here... // Do the leaking operations here...

+ 19
- 0
elmo/utils.py View File



### Binary operations ### Binary operations
def hweight(n): def hweight(n):
""" Return the Hamming weight of 'n' """
c = 0 c = 0
while n>0: while n>0:
c += (n & 1) c += (n & 1)
return c return c


def hdistance(x,y): def hdistance(x,y):
""" Return the Hamming distance between 'x' and 'y' """
return hweight(x^y) return hweight(x^y)


def binary_writing(n, nb_bits=32, with_hamming=False): def binary_writing(n, nb_bits=32, with_hamming=False):
""" Return the binary writing 'w' of 'n' with 'nb_bits'
If with_hamming is True, return a couple (w, h) with 'h' the Hamming weight of 'n'
"""
n = np.array(n) n = np.array(n)
w, h = np.zeros((nb_bits, len(n))), np.zeros((len(n))) w, h = np.zeros((nb_bits, len(n))), np.zeros((len(n)))


### Conversion ### Conversion
def to_hex(v, nb_bits=16): def to_hex(v, nb_bits=16):
""" Convert the value 'v' into a hexadecimal string (without the prefix 0x)"""
try: try:
v_hex = v.hex() v_hex = v.hex()
except AttributeError: except AttributeError:
return '0'*(nb_bits//4-len(v_hex)) + v_hex return '0'*(nb_bits//4-len(v_hex)) + v_hex


def split_octet(hexstr): def split_octet(hexstr):
""" Split a hexadecimal string (without the prefix 0x)
into a list of bytes described with a 2-length hexadecimal string
"""
return [hexstr[i:i+2] for i in range(0, len(hexstr), 2)] return [hexstr[i:i+2] for i in range(0, len(hexstr), 2)]


def to_signed_hex(v, nb_bits=16): def to_signed_hex(v, nb_bits=16):
""" Convert the signed value 'v'
into a list of bytes described with a 2-length hexadecimal string
"""
try: try:
return split_octet(to_hex(v & ((1<<nb_bits)-1), nb_bits=nb_bits)) return split_octet(to_hex(v & ((1<<nb_bits)-1), nb_bits=nb_bits))
except TypeError as err: except TypeError as err:


### Write function ### Write function
def write(_input, uint, nb_bits=16): def write(_input, uint, nb_bits=16):
""" Write in '_input' the value 'uint' by writing each byte
with hexadecimal format in a new line
"""
uint = to_signed_hex(uint, nb_bits=nb_bits) uint = to_signed_hex(uint, nb_bits=nb_bits)
for i in range(nb_bits//8): for i in range(nb_bits//8):
_input.write(uint[i]+'\n') _input.write(uint[i]+'\n')
def write_list(_input, uint_list, nb_bits=16): def write_list(_input, uint_list, nb_bits=16):
""" Write in '_input' the list of values 'uint_list' by writing each byte
with hexadecimal format in a new line
"""
for uint in uint_list: for uint in uint_list:
write(_input, uint, nb_bits=nb_bits) write(_input, uint, nb_bits=nb_bits)


### Print Utils ### Print Utils
class Color: class Color:
""" Color codes to print colored text in stdout """
HEADER = '\033[95m' HEADER = '\033[95m'
OKBLUE = '\033[94m' OKBLUE = '\033[94m'
OKCYAN = '\033[96m' OKCYAN = '\033[96m'

+ 12
- 0
test.py View File

assert res['nb_traces'] == 10 assert res['nb_traces'] == 10
assert res['nb_instructions'] assert res['nb_instructions']


# Test the methods for analysis
multiplication_indexes = simulation.get_indexes_of(lambda instr: 'mul' in instr)
asmtrace = simulation.get_asmtrace()
for index, instr in enumerate(asmtrace):
assert ('mul' in instr) == (index in multiplication_indexes)

traces = simulation.get_traces()
traces = simulation.get_traces(multiplication_indexes)
assert traces.shape == (10, len(multiplication_indexes))

printed_data = simulation.get_printed_data()

print_success(' - Test 3 "Use A Real Simulation": Success!') print_success(' - Test 3 "Use A Real Simulation": Success!')


######################################################### #########################################################

Loading…
Cancel
Save