diff --git a/docs/tutorial_01_discovering_containers.rst b/docs/tutorial_01_discovering_containers.rst index 6e0c11f..0d2449f 100644 --- a/docs/tutorial_01_discovering_containers.rst +++ b/docs/tutorial_01_discovering_containers.rst @@ -138,15 +138,15 @@ instance. # To work only with the first 10 samples: batch.leakage_section = range(10) - print(trace_batch) + print(batch) # To work with only with one tenth of the sample: batch.leakage_section = range(0, 100, 10) - print(trace_batch) + print(batch) # To cancel `leakage_section`: batch.leakage_section = None # cancelling leakage_section - print(trace_batch) + print(batch) This will output: @@ -185,7 +185,7 @@ See :code:`lascar/tools/processing` for a list of existing processing. ) # Principal component analysis on leakage with 3 components - batch.leakage_processing = PcaProcessing(trace_batch, 3) + batch.leakage_processing = PcaProcessing(batch, 3) # No leakage processing batch.leakage_processing = None diff --git a/docs/tutorial_04_acquisition_setup_example.rst b/docs/tutorial_04_acquisition_setup_example.rst index 30b44af..b87df95 100644 --- a/docs/tutorial_04_acquisition_setup_example.rst +++ b/docs/tutorial_04_acquisition_setup_example.rst @@ -45,9 +45,9 @@ oscilloscope. """ :param number_of_traces: Number of traces in the container """ - super().__init__(self, number_of_traces) self.dut = Dut() self.oscilloscope = Oscilloscope() + super().__init__(number_of_traces) def generate_trace(self, index: int): """ diff --git a/docs/tutorial_07_dpa_example.rst b/docs/tutorial_07_dpa_example.rst index c99cbd5..e6bca7c 100644 --- a/docs/tutorial_07_dpa_example.rst +++ b/docs/tutorial_07_dpa_example.rst @@ -43,7 +43,7 @@ conditioned by a single key byte (256 guesses). return sbox[value["plaintext"][3] ^ guess] & 1 guess_range = range(256) - dpa_engine = DpaEngine("dpa", selection_function, guess_range) + dpa_engine = DpaEngine(selection_function, guess_range) We can now create a :class:`Session `, register the :code:`dpa_lsb_engine`, and run it: diff --git a/docs/tutorial_08_session_outputs.rst b/docs/tutorial_08_session_outputs.rst index 9810e76..dc985b8 100644 --- a/docs/tutorial_08_session_outputs.rst +++ b/docs/tutorial_08_session_outputs.rst @@ -69,10 +69,10 @@ of the output of the 3rd sbox, the other on the MSB. return (sbox[value["plaintext"][3] ^ guess] >> 7) & 1 dpa_lsb_engine = DpaEngine( - "dpa_lsb", selection_function_lsb, guess_range, solution=container.key[3] + selection_function_lsb, guess_range, solution=container.key[3], name="dpa_lsb" ) dpa_msb_engine = DpaEngine( - "dpa_msb", selection_function_lsb, guess_range, solution=container.key[3] + selection_function_msb, guess_range, solution=container.key[3], name="dpa_msb" ) Dictionnary output diff --git a/examples/ascad/02-snr.py b/examples/ascad/02-snr.py index 98aecba..47bd910 100644 --- a/examples/ascad/02-snr.py +++ b/examples/ascad/02-snr.py @@ -36,7 +36,7 @@ container = Hdf5Container( filename, leakages_dataset_name="traces", values_dataset_name="metadata" ) -container.number_of_traces = 5000 # only 5000 traces used over the 60000 available +container = container.limited(5000) # Only 5000 traces used over the 60000 available """ @@ -47,33 +47,33 @@ - the partition_values (range(256) for all of them, since we look at byte values) """ snr_1_engine = SnrEngine( - "SNR1: unmasked sbox output", lambda value: sbox[value["plaintext"][3] ^ value["key"][3]], # sbox(p[3] ⊕ k[3]) range(256), + name="SNR1: unmasked sbox output" ) snr_2_engine = SnrEngine( - "SNR2: masked sbox output", lambda value: sbox[value["plaintext"][3] ^ value["key"][3]] ^ value["masks"][15], # sbox(p[3] ⊕ k[3]) ⊕ rout range(256), + name="SNR2: masked sbox output" ) snr_3_engine = SnrEngine( - "SNR3: common output mask out", lambda value: value["masks"][15], range(256) # rout + lambda value: value["masks"][15], range(256), name="SNR3: common output mask out" # rout ) snr_4_engine = SnrEngine( - "SNR4: masked sbox output in linear parts", lambda value: sbox[value["plaintext"][3] ^ value["key"][3]] ^ value["masks"][1], # sbox(p[3] ⊕ k[3]) ⊕ r[3] range(256), + name="SNR4: masked sbox output in linear parts" ) snr_5_engine = SnrEngine( - "SNR5: sbox output mask in linear parts", lambda value: value["masks"][1], # r[3] range(256), + name="SNR5: sbox output mask in linear parts" ) engines = [snr_1_engine, snr_2_engine, snr_3_engine, snr_4_engine, snr_5_engine] diff --git a/examples/ascad/03-keras-train.py b/examples/ascad/03-keras-train.py index 83c6be8..b19a955 100644 --- a/examples/ascad/03-keras-train.py +++ b/examples/ascad/03-keras-train.py @@ -70,13 +70,12 @@ def partition_function(value): # An engine has to be created, dedicated to the profiling of a classifier (here a keras neural network) with leakages/labels. nn_profile_engine = ProfileEngine( - "nn_profile", nn, partition_function, partition_values, epochs=5, batch_size=200, - test_size=0.1, + test_size=0.1 ) Session(profiling_container, engine=nn_profile_engine).run() diff --git a/examples/ascad/04-keras-test.py b/examples/ascad/04-keras-test.py index c55bdde..0dd66b2 100644 --- a/examples/ascad/04-keras-test.py +++ b/examples/ascad/04-keras-test.py @@ -71,15 +71,15 @@ def guess_function(value, guess): guess_range = range(256) # The possbles value for the guess # An engine has to be created, dedicated to use a classifier (here a keras neural network nn) with Side-Channel Traces. -nn_match_engine = MatchEngine( - "nn_match", nn, guess_function, guess_range, solution=solution -) +nn_match_engine = MatchEngine(nn, guess_function, guess_range, solution=solution, name="nn_match") # Now, 5 times in a row we randomly take 2000 traces from attack_container, and compute the mean rank of the correct key, every 10 traces. ranks = [] -for i,container in enumerate(split_container(attack_container, size_of_splits=2000)[:5]): +for i, container in enumerate( + split_container(attack_container, size_of_splits=2000)[:5] +): session = Session( container, diff --git a/examples/ascad/05-cpa-high-order.py b/examples/ascad/05-cpa-high-order.py index e240160..3bdf971 100644 --- a/examples/ascad/05-cpa-high-order.py +++ b/examples/ascad/05-cpa-high-order.py @@ -40,7 +40,7 @@ leakage_section=poi, ) -container.number_of_traces = 500 +container = container.limited(500) # Then we make a container that will apply a CenteredProduct to recombine all the points of interest. container.leakage_processing = CenteredProductProcessing(container) @@ -48,10 +48,10 @@ # Now a classical CPA, targetting the output of the 3rd Sbox: cpa_engine = CpaEngine( - "cpa-high-order", lambda value, guess: hamming(sbox[value["plaintext"][3] ^ guess]), range(256), solution=container[0].value["key"][3], + name="cpa-high-order", jit=False, ) diff --git a/examples/base/lra.py b/examples/base/lra.py index 5305137..134ef11 100644 --- a/examples/base/lra.py +++ b/examples/base/lra.py @@ -18,14 +18,8 @@ def guess_function( guess_range = range(256) leakage_model = HammingPrecomputedModel() - -container = BasicAesSimulationContainer( - 5000, 0 -) - -attack = LraEngine( - "lra", partition, partition_size, guess_function, guess_range -) +container = BasicAesSimulationContainer(5000, 0) +attack = LraEngine("lra", partition, partition_size, guess_function, guess_range) session = Session(container) session.add_engine(attack) diff --git a/examples/base/ttest.py b/examples/base/ttest.py index 0339285..3e7806a 100644 --- a/examples/base/ttest.py +++ b/examples/base/ttest.py @@ -41,7 +41,6 @@ def partition_function(value): return partition_function -number_of_partitions = 2 # number of possible classes (~output of the partiton_function) for the partition_function ttest_engines = [ TTestEngine(get_partition_function(i)) for i in range(16) ] diff --git a/examples/rainbow/rainbow_stm32.py b/examples/rainbow/rainbow_stm32.py index b5bd258..9136487 100644 --- a/examples/rainbow/rainbow_stm32.py +++ b/examples/rainbow/rainbow_stm32.py @@ -111,7 +111,6 @@ def generate_trace(self, idx): container = RainbowSubBytesContainer(250) engine = CpaEngine( - "cpa byte 5", lambda value, secret: sbox[value["plaintext"][5] ^ secret], range(256), solution=42, diff --git a/lascar/container/__init__.py b/lascar/container/__init__.py index 95c2592..6053be3 100644 --- a/lascar/container/__init__.py +++ b/lascar/container/__init__.py @@ -24,6 +24,7 @@ from .container import AbstractContainer from .container import Container from .container import TraceBatchContainer +from .container import Slice from .filtered_container import FilteredContainer from .filtered_container import RandomizedContainer from .filtered_container import split_container diff --git a/lascar/container/container.py b/lascar/container/container.py index 69b2c10..d2db09d 100644 --- a/lascar/container/container.py +++ b/lascar/container/container.py @@ -109,10 +109,10 @@ class Container: Where both leakage and value can be represented as a numpy array. - The role of the Container class is to be overloaded so that it can deliver traces, stored as a specified format. - Mostly, the __getitem__/__setitem__ have to be overloaded when user want to write its own Container format class. - - :param number_of_traces: + The role of the Container class is to be overloaded so that it can deliver traces, + stored as a specified format. Mostly, the `__getitem__`, `__setitem__` and + `__len__` methods have to be overloaded when user want to write its own Container + format class. """ def __init__(self, **kwargs): @@ -149,9 +149,6 @@ def __init__(self, **kwargs): ) self._value_abstract = AbstractArray(self.values.shape[1:], self.values.dtype) - self.number_of_traces = kwargs.get( - "number_of_traces", self.number_of_traces_max - ) self.leakage_section = kwargs.get("leakage_section", None) self.value_section = kwargs.get("value_section", None) self.leakage_processing = kwargs.get("leakage_processing", None) @@ -345,9 +342,6 @@ def plot_leakage(self, key): plot([self[i].leakage for i in key]) - def __len__(self): - return self.number_of_traces - def __iter__(self): """ Container is iterable @@ -359,7 +353,7 @@ def __iter__(self): yield self[i] def __str__(self): - res = "Container with %d traces. " % (self.number_of_traces) + res = f"Container with {len(self)} traces. " res += "leakages: %s, values: %s. " % ( self._leakage_abstract, self._value_abstract, @@ -396,6 +390,25 @@ def get_leakage_mean_var(self): session = Session(self).run() return session["mean"].finalize(), session["var"].finalize() + def sliced(self, start: int, stop: int) -> "Slice": + """ + Wraps the current container in a :class:`Slice` to select a slice of the + traces. + + :param start: Start index + :param stop: End index, excluded. + """ + return Slice(self, start, stop) + + def limited(self, count: int) -> "Slice": + """ + Wraps the current container in a :class:`Slice` to limit the number of + traces. + + :param count: Number of traces limit. + """ + return Slice(self, 0, count) + # def to_trace(func): # def wrapper(*args, **kwargs): @@ -423,7 +436,7 @@ class AbstractContainer(Container): def __init__(self, number_of_traces, **kwargs): self.logger = logging.getLogger(__name__) - + self.number_of_traces = number_of_traces trace = self.generate_trace(0) self.leakages = AbstractArray( (number_of_traces,) + trace.leakage.shape, trace.leakage.dtype @@ -431,8 +444,7 @@ def __init__(self, number_of_traces, **kwargs): self.values = AbstractArray( (number_of_traces,) + trace.value.shape, trace.value.dtype ) - - Container.__init__(self, **kwargs) + super().__init__(**kwargs) def generate_trace(self, idx): """ @@ -466,6 +478,9 @@ def generate_trace_batch(self, idx_begin, idx_end): return TraceBatchContainer(leakages, values) + def __len__(self): + return self.number_of_traces + def __getitem__(self, key): """ :rtype: Trace or TraceBatch depending on key @@ -577,6 +592,9 @@ def __setitem__(self, key, value): self.leakages[key] = value.leakage if isinstance(key, int) else value.leakages self.values[key] = value.value if isinstance(key, int) else value.values + def __len__(self): + return len(self.leakages) + def save(self, filename): """ Save the current TraceBatchContainer to a file using np.save @@ -722,3 +740,38 @@ def generate_trace(self, idx): leakage = self.get_leakage() return Trace(leakage, value) + + +class Slice: + def __init__(self, base: Container, start: int, stop: int): + """ + Container which is a slice of a parent container, in order to reduce the + number of traces. + + :param base: Base container + :param start: Start index + :param stop: End index, excluded. + """ + self.base = base + if stop < start: + raise ValueError("Slice stop must be >= start") + self.start = start + self.stop = stop + + def __getitem__(self, key): + if isinstance(key, int): + if key < self.start or key >= self.stop: + raise IndexError() + return self.base[key] + elif isinstance(key, slice): + if key.start < self.start or key.stop > self.stop: + raise IndexError() + return self.base[key] + elif isinstance(key, list): + for i in key: + if i < self.start or i >= self.stop: + raise IndexError() + return self.base[key] + + def __len__(self): + return self.stop - self.start diff --git a/lascar/container/filtered_container.py b/lascar/container/filtered_container.py index 1cb5e37..b0d6303 100644 --- a/lascar/container/filtered_container.py +++ b/lascar/container/filtered_container.py @@ -73,7 +73,7 @@ class RandomizedContainer(FilteredContainer): def __init__(self, container, **kwargs): FilteredContainer.__init__( - self, container, np.random.permutation(container.number_of_traces), **kwargs + self, container, np.random.permutation(len(container)), **kwargs ) @@ -84,7 +84,7 @@ def split_container(container, random=True, **kwargs): :param kwargs: specify either the number of splits (number_of_splits), or the size of each split (size_of_splits) :return: a list of number_of_splits containers OR a list of containers of size_of_splits each. """ - n = container.number_of_traces + n = len(container) if random: indexes = np.random.permutation(n) else: diff --git a/lascar/container/hdf5_container.py b/lascar/container/hdf5_container.py index 3e535d7..f39025e 100644 --- a/lascar/container/hdf5_container.py +++ b/lascar/container/hdf5_container.py @@ -71,6 +71,9 @@ def __getitem__(self, key): def __setitem__(self, key, value): TraceBatchContainer.__setitem__(self, key, value) + def __len__(self): + return len(self.leakages) + @staticmethod def void_container( filename, @@ -143,7 +146,7 @@ def export( leakage, value = container[0] out = Hdf5Container.void_container( filename, - container.number_of_traces, + len(container), leakage.shape, leakage.dtype, value.shape, diff --git a/lascar/container/multiple_container.py b/lascar/container/multiple_container.py index 5484fe0..67dc5b8 100644 --- a/lascar/container/multiple_container.py +++ b/lascar/container/multiple_container.py @@ -44,7 +44,7 @@ def __init__(self, *args, **kwargs): self._containers[0].values.dtype, ) - Container.__init__(self, **kwargs) + super().__init__(**kwargs) self.logger.debug( "Creating MultipleContainer using %d Container." % len(self._containers) ) @@ -53,12 +53,11 @@ def __init__(self, *args, **kwargs): current = 0 self._t = np.zeros((self.number_of_traces + 1, 2), int) for i, container in enumerate(args): - self._t[current : current + container.number_of_traces, 0] = i - self._t[current : current + container.number_of_traces, 1] = range( - container.number_of_traces - ) - current += container.number_of_traces - self._t[current] = i, container.number_of_traces + l = len(container) + self._t[current : current + l, 0] = i + self._t[current : current + l, 1] = range(l) + current += l + self._t[current] = i, l def __getitem__(self, item): @@ -111,11 +110,11 @@ def __getitem__(self, item): [ container_offset_begin, suboffset_offset_begin, - self._containers[container_offset_begin].number_of_traces, + len(self._containers[container_offset_begin]), ] ) for i in range(container_offset_begin + 1, container_offset_end): - suboffsets.append([i, 0, self._containers[i].number_of_traces]) + suboffsets.append([i, 0, len(self._containers[i])]) suboffsets.append([container_offset_end, 0, suboffset_offset_end]) # TODO: Find a better solution... @@ -158,6 +157,12 @@ def __getitem__(self, item): # i += len(batch) return TraceBatchContainer(leakages, values) + def __len__(self): + acc = 0 + for container in self._containers: + acc += len(container) + return acc + @property def leakage_section(self): return self._leakage_section diff --git a/lascar/container/npy_container.py b/lascar/container/npy_container.py index 4de61f6..447a0eb 100644 --- a/lascar/container/npy_container.py +++ b/lascar/container/npy_container.py @@ -55,6 +55,9 @@ def __getitem__(self, key): def __setitem__(self, key, value): TraceBatchContainer.__setitem__(self, key, value) + def __len__(self): + return len(self.leakages) + def _void_container( leakages_filename, values_filename, @@ -108,7 +111,7 @@ def export( out = NpyContainer._void_container( leakages_filename, values_filename, - container.number_of_traces, + len(container), leakage.shape, leakage.dtype, value.shape, diff --git a/lascar/engine/classifier_engine.py b/lascar/engine/classifier_engine.py index 4e4507a..648be1f 100644 --- a/lascar/engine/classifier_engine.py +++ b/lascar/engine/classifier_engine.py @@ -87,8 +87,7 @@ def __init__( self.batch_size = batch_size def _initialize(self): - - self._session._batch_size = self._session.container.number_of_traces + self._session._batch_size = len(self._session.container) self._session._thread_on_update = False if self.classifier_type == "keras": self._session._progressbar = None @@ -161,7 +160,7 @@ def __init__( def _initialize(self): self._log_probas = np.zeros((self._number_of_guesses,)) - self._session._batch_size = self._session.container.number_of_traces + self._session._batch_size = len(self._session.container) self._session._thread_on_update = False def _update(self, batch): diff --git a/lascar/engine/lra_engine.py b/lascar/engine/lra_engine.py index 4401ad0..f1a2092 100644 --- a/lascar/engine/lra_engine.py +++ b/lascar/engine/lra_engine.py @@ -72,8 +72,8 @@ def __init__( :param regression_order: the regression order (by default =1) :param size_target_value: the number of bits of the target value, that we are regressing (by default = 8). """ - PartitionerEngine.__init__(self, name, partition_function, partition_range, 2) - GuessEngine.__init__(self, name, selection_function, guess_range, solution) + PartitionerEngine.__init__(self, partition_function, partition_range, 2, name=name) + GuessEngine.__init__(self, selection_function, guess_range, solution=solution, name=name) self.logger.debug( 'Creating LraEngine "%s" with %d partitions, %d guesses.' % (name, len(self._partition_range), len(guess_range)) diff --git a/lascar/session.py b/lascar/session.py index 2fb83e4..d1dcdd9 100644 --- a/lascar/session.py +++ b/lascar/session.py @@ -68,8 +68,10 @@ def __init__( self.logger.debug("Creating Session.") self.container = container - self.leakage_shape = container._leakage_abstract.shape - self.value_shape = container._value_abstract.shape + + leakage_0, value_0 = container[0] + self.leakage_shape = leakage_0.shape + self.value_shape = value_0.shape self.name = name @@ -115,15 +117,15 @@ def output_steps(self): def output_steps(self, output_steps): if isinstance(output_steps, int): self._output_steps = list( - range(output_steps, self.container.number_of_traces + 1, output_steps) + range(output_steps, len(self.container) + 1, output_steps) ) elif hasattr(output_steps, "__iter__"): self._output_steps = [i for i in output_steps] else: self._output_steps = [] - if not self.container.number_of_traces in self._output_steps: - self._output_steps.append(self.container.number_of_traces) + if not len(self.container) in self._output_steps: + self._output_steps.append(len(self.container)) self._output_steps.sort() def add_engine(self, engine): @@ -159,9 +161,9 @@ def _generate_batch_offsets(self, batch_size): batch_offsets = [] offset = 0 - while offset < self.container.number_of_traces: - if offset + batch_size > self.container.number_of_traces: - batch_offsets.append((offset, self.container.number_of_traces)) + while offset < len(self.container): + if offset + batch_size > len(self.container): + batch_offsets.append((offset, len(self.container))) else: batch_offsets.append((offset, offset + batch_size)) @@ -226,7 +228,7 @@ def run(self, batch_size=100, thread_on_update=True): "Session %s: %d traces, %d engines, batch_size=%d, leakage_shape=%s" % ( self.name, - self.container.number_of_traces, + len(self.container), len(self.engines), self._batch_size, self.leakage_shape, diff --git a/lascar/tools/processing.py b/lascar/tools/processing.py index 1896078..d786b93 100644 --- a/lascar/tools/processing.py +++ b/lascar/tools/processing.py @@ -76,7 +76,7 @@ def __init__(self, container, rois=None, order=2, batch_size=1000, filename=None session = Session(container, name="CenteredProduct:") session.run(batch_size) self.mean = session["mean"].finalize() - number_of_leakage_samples = container._leakage_abstract.shape[0] + number_of_leakage_samples = container[0][0].shape[0] if rois is None: self.order = order @@ -119,7 +119,7 @@ def __init__( self._pca = PCA(n_components=number_of_components, random_state=random_state) # compute pca: - batch = container[: container.number_of_traces] + batch = container[: len(container)] self._pca.fit(batch.leakages) self.post_section = post_section @@ -156,7 +156,7 @@ def __init__( ) # compute ica: - batch = container[: container.number_of_traces] + batch = container[: len(container)] self._ica.fit(batch.leakages) self.post_section = post_section diff --git a/tests/test_container.py b/tests/test_container.py index 751fa1a..c59b26a 100644 --- a/tests/test_container.py +++ b/tests/test_container.py @@ -112,7 +112,7 @@ def test_leakage_processing_standard_scaler(self, container): leakage_processing = StandardScalerProcessing(container) container.leakage_processing = leakage_processing - batch = container[: container.number_of_traces] + batch = container[: len(container)] leakages_centered_reduced = (leakages - leakages.mean(0)) / leakages.std(0) assert np.all(np.isclose(batch.leakages, leakages_centered_reduced)) @@ -143,7 +143,7 @@ def test_leakage_processing_centered_product(self, container, order): container.leakage_processing = leakage_processing leakages_centered = leakages - leakages.mean(0) - batch = container[: container.number_of_traces] + batch = container[: len(container)] for i, combination in enumerate(leakage_processing.combinations): assert np.all( batch.leakages[:, i] diff --git a/tutorial/01-discovering-containers.py b/tutorial/01-discovering-containers.py index a28cbcb..b977cbd 100644 --- a/tutorial/01-discovering-containers.py +++ b/tutorial/01-discovering-containers.py @@ -114,7 +114,7 @@ print(batch) # Principal component analysis on leakage with 3 components -batch.leakage_processing = PcaProcessing(trace_batch, 3) +batch.leakage_processing = PcaProcessing(batch, 3) print(batch) # No leakage processing diff --git a/tutorial/04-acquisition-setup-example.py b/tutorial/04-acquisition-setup-example.py index 174e688..68ba566 100644 --- a/tutorial/04-acquisition-setup-example.py +++ b/tutorial/04-acquisition-setup-example.py @@ -42,9 +42,9 @@ def __init__(self, number_of_traces: int): """ :param number_of_traces: Number of traces in the container """ - super().__init__(self, number_of_traces) self.dut = Dut() self.oscilloscope = Oscilloscope() + super().__init__(number_of_traces) def generate_trace(self, index: int): """ @@ -66,8 +66,8 @@ def generate_trace(self, index: int): # This container is Abstract. Its 100 traces are stored nowhere, yet they can be # accessed: -print("trace 0:", acquisition_container[0]) -print("trace 10:", acquisition_container[10]) +print("trace 0:", acquisition[0]) +print("trace 10:", acquisition[10]) # More importantly, this container can be converted to a Hdf5Container, so the # traces get saved to the disk. The export method takes here all its sense. diff --git a/tutorial/07-session-dpa-example.py b/tutorial/07-session-dpa-example.py index 0761920..785d7f7 100644 --- a/tutorial/07-session-dpa-example.py +++ b/tutorial/07-session-dpa-example.py @@ -33,7 +33,7 @@ def selection_function(value, guess): guess_range = range(256) -dpa_engine = DpaEngine("dpa", selection_function, guess_range) +dpa_engine = DpaEngine(selection_function, guess_range) # We can now create a Session, register the dpa_lsb_engine, and run it. diff --git a/tutorial/08-session-manage-outputs.py b/tutorial/08-session-manage-outputs.py index 654ae15..53e52a7 100644 --- a/tutorial/08-session-manage-outputs.py +++ b/tutorial/08-session-manage-outputs.py @@ -56,10 +56,10 @@ def selection_function_msb(value, guess): dpa_lsb_engine = DpaEngine( - "dpa_lsb", selection_function_lsb, guess_range, solution=container.key[3] + selection_function_lsb, guess_range, solution=container.key[3], name="dpa_lsb" ) dpa_msb_engine = DpaEngine( - "dpa_msb", selection_function_lsb, guess_range, solution=container.key[3] + selection_function_msb, guess_range, solution=container.key[3], name="dpa_msb" ) # DictOutputMethod: