diff --git a/analysis/ceca.py b/analysis/ceca.py index 02b2a84d..26244046 100755 --- a/analysis/ceca.py +++ b/analysis/ceca.py @@ -2,6 +2,16 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 +"""A distributed implementation of the correlation-enhanced power analysis +collision attack. + +See "Correlation-Enhanced Power Analysis Collision Attack" by A. Moradi, O. +Mischke, and T. Eisenbarth (https://eprint.iacr.org/2010/297.pdf) for more +information. + +Typical usage: +>>> ./ceca.py -f PROJECT_FILE -n 400000 -w 5 -a 117 127 -d output -s 3 +""" import argparse import enum @@ -25,22 +35,12 @@ from capture.project_library.project import ProjectConfig # noqa : E402 from capture.project_library.project import SCAProject # noqa : E402 -"""A distributed implementation of the correlation-enhanced power analysis -collision attack. - -See "Correlation-Enhanced Power Analysis Collision Attack" by A. Moradi, O. -Mischke, and T. Eisenbarth (https://eprint.iacr.org/2010/297.pdf) for more -information. - -Typical usage: ->>> ./ceca.py -f PROJECT_FILE -n 400000 -w 5 -a 117 127 -d output -s 3 -""" - def timer(): """A customization of the ``codetiming.Timer`` decorator.""" def decorator(func): + @codetiming.Timer( name=func.__name__, text=f"{func.__name__} took {{seconds:.1f}}s", @@ -79,7 +79,8 @@ class TraceWorker: >>> results = ray.get(tasks) """ - def __init__(self, project_file, trace_slice, attack_window, attack_direction): + def __init__(self, project_file, trace_slice, attack_window, + attack_direction): """Inits a TraceWorker. Args: @@ -94,9 +95,10 @@ def __init__(self, project_file, trace_slice, attack_window, attack_direction): project_type = "ot_trace_library" # Open the project. - project_cfg = ProjectConfig( - type=project_type, path=project_file, wave_dtype=np.uint16, overwrite=False - ) + project_cfg = ProjectConfig(type=project_type, + path=project_file, + wave_dtype=np.uint16, + overwrite=False) self.project = SCAProject(project_cfg) self.project.open_project() @@ -104,16 +106,16 @@ def __init__(self, project_file, trace_slice, attack_window, attack_direction): self.num_samples = attack_window.stop - attack_window.start if attack_direction == AttackDirection.INPUT: self.texts = np.vstack( - self.project.get_plaintexts(trace_slice.start, trace_slice.stop) - ) + self.project.get_plaintexts(trace_slice.start, + trace_slice.stop)) else: self.texts = np.vstack( - self.project.get_ciphertexts(trace_slice.start, trace_slice.stop) - ) + self.project.get_ciphertexts(trace_slice.start, + trace_slice.stop)) self.traces = np.asarray( - self.project.get_waves(trace_slice.start, trace_slice.stop) - )[:, attack_window] + self.project.get_waves(trace_slice.start, + trace_slice.stop))[:, attack_window] self.project.close(save=False) @@ -132,7 +134,7 @@ def compute_stats(self): cnt = self.traces.shape[0] sum_ = self.traces.sum(axis=0) mean = sum_ / cnt - sum_dev_prods = ((self.traces - mean) ** 2).sum(axis=0) + sum_dev_prods = ((self.traces - mean)**2).sum(axis=0) return (cnt, sum_, sum_dev_prods) def filter_noisy_traces(self, min_trace, max_trace): @@ -146,8 +148,7 @@ def filter_noisy_traces(self, min_trace, max_trace): Number of remaining traces. """ traces_to_use = np.all( - (self.traces >= min_trace) & (self.traces <= max_trace), axis=1 - ) + (self.traces >= min_trace) & (self.traces <= max_trace), axis=1) self.traces = self.traces[traces_to_use] self.texts = self.texts[traces_to_use] return self.traces.shape[0] @@ -214,12 +215,12 @@ def compute_mean_and_std(workers): running_cnt += cnt else: running_sum_dev_prods += sum_dev_prods + ( - (cnt * running_sum - running_cnt * sum_) ** 2 / - (cnt * running_cnt * (cnt + running_cnt)) - ) + (cnt * running_sum - running_cnt * sum_)**2 / + (cnt * running_cnt * (cnt + running_cnt))) running_sum += sum_ running_cnt += cnt - return running_sum / running_cnt, np.sqrt(running_sum_dev_prods / running_cnt) + return running_sum / running_cnt, np.sqrt(running_sum_dev_prods / + running_cnt) def filter_noisy_traces(workers, mean_trace, std_trace, max_std): @@ -237,7 +238,8 @@ def filter_noisy_traces(workers, mean_trace, std_trace, max_std): min_trace = mean_trace - max_std * std_trace max_trace = mean_trace + max_std * std_trace tasks = [ - worker.filter_noisy_traces.remote(min_trace, max_trace) for worker in workers + worker.filter_noisy_traces.remote(min_trace, max_trace) + for worker in workers ] running_cnt = 0 @@ -392,7 +394,8 @@ def find_best_diffs(pairwise_diffs_scores): # the most likely differences between key bytes. G.add_edge(a, b, weight=DiffScore(pairwise_diffs_scores[a, b, 1])) # Find paths from key byte 0 to all other bytes. - paths = nx.algorithms.shortest_paths.weighted.single_source_dijkstra_path(G, 0) + paths = nx.algorithms.shortest_paths.weighted.single_source_dijkstra_path( + G, 0) # Recover the paths and corresponding differences from key byte 0 to all # other bytes. diffs = np.zeros(16, dtype=np.uint8) @@ -422,9 +425,11 @@ def recover_key(diffs, attack_direction, plaintext, ciphertext): # Create a matrix of all possible keys. keys = np.zeros((256, 16), np.uint8) for first_byte_val in range(256): - key = np.asarray([diffs[i] ^ first_byte_val for i in range(16)], np.uint8) + key = np.asarray([diffs[i] ^ first_byte_val for i in range(16)], + np.uint8) if attack_direction == AttackDirection.OUTPUT: - key = np.asarray(cwa.aes_funcs.key_schedule_rounds(key, 10, 0), np.uint8) + key = np.asarray(cwa.aes_funcs.key_schedule_rounds(key, 10, 0), + np.uint8) keys[first_byte_val] = key # Encrypt the plaintext using all candidates in parallel. ciphertexts = scared.aes.base.encrypt(plaintext, keys) @@ -464,9 +469,8 @@ def compare_diffs(pairwise_diffs_scores, attack_direction, correct_key): @timer() -def perform_attack( - project_file, num_traces, attack_window, attack_direction, max_std, num_workers -): +def perform_attack(project_file, num_traces, attack_window, attack_direction, + max_std, num_workers): """Performs a correlation-enhanced power analysis collision attack. This function: @@ -506,9 +510,10 @@ def perform_attack( project_type = "ot_trace_library" # Open the project. - project_cfg = ProjectConfig( - type=project_type, path=project_file, wave_dtype=np.uint16, overwrite=False - ) + project_cfg = ProjectConfig(type=project_type, + path=project_file, + wave_dtype=np.uint16, + overwrite=False) project = SCAProject(project_cfg) project.open_project() @@ -524,11 +529,11 @@ def perform_attack( f"Invalid attack window: {attack_window} (must be in [0, {last_sample}])" ) if max_std <= 0: - raise ValueError(f"Invalid max_std: {max_std} (must be greater than zero)") + raise ValueError( + f"Invalid max_std: {max_std} (must be greater than zero)") if num_workers <= 0: raise ValueError( - f"Invalid num_workers: {num_workers} (must be greater than zero)" - ) + f"Invalid num_workers: {num_workers} (must be greater than zero)") # Instantiate workers def worker_trace_slices(): @@ -539,15 +544,15 @@ def worker_trace_slices(): traces_per_worker = int(num_traces / num_workers) first_worker_num_traces = traces_per_worker + num_traces % num_workers yield slice(0, first_worker_num_traces) - for trace_begin in range( - first_worker_num_traces, num_traces, traces_per_worker - ): + for trace_begin in range(first_worker_num_traces, num_traces, + traces_per_worker): yield slice(trace_begin, trace_begin + traces_per_worker) # Attack window is inclusive. attack_window = slice(attack_window[0], attack_window[1] + 1) workers = [ - TraceWorker.remote(project_file, trace_slice, attack_window, attack_direction) + TraceWorker.remote(project_file, trace_slice, attack_window, + attack_direction) for trace_slice in worker_trace_slices() ] assert len(workers) == num_workers @@ -556,10 +561,8 @@ def worker_trace_slices(): # Filter noisy traces. orig_num_traces = num_traces num_traces = filter_noisy_traces(workers, mean, std_dev, max_std) - logging.info( - f"Will use {num_traces} traces " - f"({100 * num_traces / orig_num_traces:.1f}% of all traces)" - ) + logging.info(f"Will use {num_traces} traces " + f"({100 * num_traces / orig_num_traces:.1f}% of all traces)") # Mean traces for all values of all text bytes. mean_text_traces = compute_mean_text_traces(workers) # Guess the differences between key bytes. @@ -567,21 +570,18 @@ def worker_trace_slices(): diffs = find_best_diffs(pairwise_diffs_scores) logging.info(f"Difference values (delta_0_i): {diffs}") # Recover the key. - key = recover_key( - diffs, attack_direction, project.get_plaintexts(0), project.get_ciphertexts(0) - ) + key = recover_key(diffs, attack_direction, project.get_plaintexts(0), + project.get_ciphertexts(0)) if key is not None: logging.info(f"Recovered AES key: {bytes(key).hex()}") else: logging.error("Failed to recover the AES key") # Compare differences - both matrices are symmetric and have an all-zero main diagonal. - correct_diffs = compare_diffs( - pairwise_diffs_scores, attack_direction, project.get_keys(0) - ) + correct_diffs = compare_diffs(pairwise_diffs_scores, attack_direction, + project.get_keys(0)) logging.info( f"Recovered {((np.sum(correct_diffs) - 16) / 2).astype(int)}/120 " - "differences between key bytes" - ) + "differences between key bytes") project.close(save=False) return key @@ -591,8 +591,7 @@ def parse_args(): parser = argparse.ArgumentParser( description="""A distributed implementation of the attack described in "Correlation-Enhanced Power Analysis Collision Attack" by A. Moradi, O. - Mischke, and T. Eisenbarth (https://eprint.iacr.org/2010/297.pdf).""" - ) + Mischke, and T. Eisenbarth (https://eprint.iacr.org/2010/297.pdf).""") parser.add_argument( "-f", "--project-file", @@ -649,8 +648,7 @@ def config_logger(): sh = logging.StreamHandler() sh.setLevel(logging.INFO) formatter = logging.Formatter( - "%(asctime)s %(levelname)s %(filename)s:%(lineno)d -- %(message)s" - ) + "%(asctime)s %(levelname)s %(filename)s:%(lineno)d -- %(message)s") sh.setFormatter(formatter) logger.addHandler(sh) return logger @@ -665,9 +663,9 @@ def main(): ray.init( runtime_env={ "working_dir": "../", - "excludes": ["*.db", "*.cwp", "*.npy", "*.bit", "*/lfs/*", "*.pack"], - } - ) + "excludes": + ["*.db", "*.cwp", "*.npy", "*.bit", "*/lfs/*", "*.pack"], + }) key = perform_attack(**vars(args)) sys.exit(0 if key is not None else 1) diff --git a/analysis/trace_preprocessing.py b/analysis/trace_preprocessing.py index 8ebe16fd..cb779718 100755 --- a/analysis/trace_preprocessing.py +++ b/analysis/trace_preprocessing.py @@ -67,20 +67,22 @@ def parse_arguments(argv): dest="align_enable", action='store_true', help="Aligtn the traces") - parser.add_argument("-s", - "--num_sigmas", - dest="num_sigmas", - type=float, - required=False, - help="Amount of tolerable deviation from average during\ + parser.add_argument( + "-s", + "--num_sigmas", + dest="num_sigmas", + type=float, + required=False, + help="Amount of tolerable deviation from average during\ filtering") - parser.add_argument("-m", - "--max_traces_mem", - dest="max_traces_mem", - type=int, - required=False, - default=10000, - help="Maximum amount of traces held in memory per process") + parser.add_argument( + "-m", + "--max_traces_mem", + dest="max_traces_mem", + type=int, + required=False, + default=10000, + help="Maximum amount of traces held in memory per process") parser.add_argument("-c", "--num_cores", dest="num_cores", @@ -95,13 +97,14 @@ def parse_arguments(argv): required=False, default=100, help="Number of traces to print") - parser.add_argument("-n", - "--num_traces_mean", - dest="num_traces_mean", - type=int, - required=False, - default=100, - help="Number of traces used for calculating mean for the\ + parser.add_argument( + "-n", + "--num_traces_mean", + dest="num_traces_mean", + type=int, + required=False, + default=100, + help="Number of traces used for calculating mean for the\ trace algining reference trace") parser.add_argument("-lw", "--low_window", @@ -121,14 +124,14 @@ def parse_arguments(argv): type=int, required=False, help="Maximum shift for trace aligning. Would more " - "shift be needed, the trace gets discarded") + "shift be needed, the trace gets discarded") args = parser.parse_args(argv) return args -def print_traces(args, project: SCAProject, filename: Path, ref_trace = None): +def print_traces(args, project: SCAProject, filename: Path, ref_trace=None): """ Print traces to file. Args: @@ -143,11 +146,11 @@ def print_traces(args, project: SCAProject, filename: Path, ref_trace = None): num_traces = metadata["num_traces"] logger.info(f"Printing {str(filename)}.html...") plot.save_plot_to_file(project.get_waves(0, num_traces), - set_indices = None, - num_traces = num_traces, - outfile = filename, + set_indices=None, + num_traces=num_traces, + outfile=filename, add_mean_stddev=False, - ref_trace = ref_trace) + ref_trace=ref_trace) def filter_traces(args: dict, project_in: SCAProject, project_out: SCAProject): @@ -186,18 +189,18 @@ def filter_traces(args: dict, project_in: SCAProject, project_out: SCAProject): min_trace = mean - args.num_sigmas * std # Filter traces. traces_to_use = np.zeros(len(in_traces), dtype=bool) - traces_to_use = np.all((in_traces >= min_trace) & - (in_traces <= max_trace), axis=1) + traces_to_use = np.all( + (in_traces >= min_trace) & (in_traces <= max_trace), axis=1) out_traces = in_traces[traces_to_use] out_ptx = in_ptx[traces_to_use] out_ctx = in_ctx[traces_to_use] out_k = in_k[traces_to_use] # Store into output project. for idx in range(len(out_traces)): - project_out.append_trace(wave = out_traces[idx], - plaintext = out_ptx[idx], - ciphertext = out_ctx[idx], - key = out_k[idx]) + project_out.append_trace(wave=out_traces[idx], + plaintext=out_ptx[idx], + ciphertext=out_ctx[idx], + key=out_k[idx]) num_filtered_traces += 1 project_out.save() # Free memory. @@ -237,24 +240,27 @@ def align_traces_process(args: dict, in_traces: list, in_ptx: list, # Create configuration for a temporary project. Needed to convert DB to # CW database as we utilize functionality from CW for the trace # alignment. - tmp_project_path = str(Path(args.project_out).parent) + "/tmp" + job_id + ".cwp" - tmp_project = ProjectConfig(type = "cw", path = tmp_project_path, - wave_dtype = np.uint16, overwrite = True, - trace_threshold = args.max_traces_mem) + tmp_project_path = str(Path( + args.project_out).parent) + "/tmp" + job_id + ".cwp" + tmp_project = ProjectConfig(type="cw", + path=tmp_project_path, + wave_dtype=np.uint16, + overwrite=True, + trace_threshold=args.max_traces_mem) cw_project = SCAProject(tmp_project) cw_project.create_project() # Convert SCAProject into ChipWhisperer project. # Insert reference mean trace into position 0 of the CW library. # This trace will be removed after processing the trace set. - cw_project.append_trace(wave = ref_trace.wave, - plaintext = ref_trace.plaintext, - ciphertext = ref_trace.ciphertext, - key = ref_trace.key) + cw_project.append_trace(wave=ref_trace.wave, + plaintext=ref_trace.plaintext, + ciphertext=ref_trace.ciphertext, + key=ref_trace.key) for idx, trace in enumerate(in_traces): - cw_project.append_trace(wave = trace, - plaintext = in_ptx[idx], - ciphertext = in_ctx[idx], - key = in_k[idx]) + cw_project.append_trace(wave=trace, + plaintext=in_ptx[idx], + ciphertext=in_ctx[idx], + key=in_k[idx]) # Align traces using CW functionality. resync_traces = cw.analyzer.preprocessing.ResyncSAD(cw_project.project) @@ -271,10 +277,10 @@ def align_traces_process(args: dict, in_traces: list, in_ptx: list, if (resync_traces.get_textin(i) is not ref_trace.plaintext and resync_traces.get_textout(i) is not ref_trace.ciphertext and resync_traces.get_known_key(i) is not ref_trace.key): - trace = Trace(wave = resync_traces.get_trace(i).astype(np.uint16), - plaintext = resync_traces.get_textin(i), - ciphertext = resync_traces.get_textout(i), - key = resync_traces.get_known_key(i)) + trace = Trace(wave=resync_traces.get_trace(i).astype(np.uint16), + plaintext=resync_traces.get_textin(i), + ciphertext=resync_traces.get_textout(i), + key=resync_traces.get_known_key(i)) traces.append(trace) cw_project.close(save=False) in_traces = [] @@ -304,15 +310,18 @@ def align_traces(args: dict, project_in: SCAProject, project_out: SCAProject): # Calculate the mean of num_traces_mean traces and use as reference # trace for the aligning. traces_mean = project_in.get_waves(1, args.num_traces_mean) - traces_new = np.empty((len(traces_mean), len(traces_mean[0])), dtype=np.uint16) + traces_new = np.empty((len(traces_mean), len(traces_mean[0])), + dtype=np.uint16) for i_trace in range(len(traces_mean)): traces_new[i_trace] = traces_mean[i_trace] ref_mean_wave = traces_new.mean(axis=0).astype(np.uint16) ref_ptx = np.zeros(len(project_in.get_plaintexts(0)), dtype=np.uint16) ref_ctx = np.zeros(len(project_in.get_ciphertexts(0)), dtype=np.uint16) ref_k = np.zeros(len(project_in.get_keys(0)), dtype=np.uint16) - ref_trace = Trace(wave = ref_mean_wave, plaintext = ref_ptx, - ciphertext = ref_ctx, key = ref_k) + ref_trace = Trace(wave=ref_mean_wave, + plaintext=ref_ptx, + ciphertext=ref_ctx, + key=ref_k) # Iterate over the traces, keep max. max_traces_mem in memory. num_traces_aligned = 0 trace_end = 0 @@ -320,8 +329,10 @@ def align_traces(args: dict, project_in: SCAProject, project_out: SCAProject): if metadata["num_traces"] < args.max_traces_mem: max_traces_mem_core = int(metadata["num_traces"] / args.num_cores) max_traces_mem_total = max_traces_mem_core * args.num_cores - for trace_it in tqdm(range(0, metadata["num_traces"], max_traces_mem_total), - desc="Aligning", ncols=80, + for trace_it in tqdm(range(0, metadata["num_traces"], + max_traces_mem_total), + desc="Aligning", + ncols=80, unit=str(max_traces_mem_total) + " traces"): # Get current trace set. trace_end = trace_it + max_traces_mem_total @@ -330,20 +341,22 @@ def align_traces(args: dict, project_in: SCAProject, project_out: SCAProject): in_ctx = project_in.get_ciphertexts(trace_it, trace_end) in_k = project_in.get_keys(trace_it, trace_end) # Distribute trace aligning to multiple processes. - aligned_traces_total = Parallel(n_jobs=args.num_cores)(delayed( - align_traces_process)(args, in_traces[i:i + max_traces_mem_core], - in_ptx[i:i + max_traces_mem_core], - in_ctx[i:i + max_traces_mem_core], - in_k[i:i + max_traces_mem_core], - ref_trace, str(trace_it + i)) + aligned_traces_total = Parallel(n_jobs=args.num_cores)( + delayed(align_traces_process) + (args, in_traces[i:i + max_traces_mem_core], + in_ptx[i:i + + max_traces_mem_core], in_ctx[i:i + + max_traces_mem_core], + in_k[i:i + max_traces_mem_core], ref_trace, str(trace_it + i)) for i in range(0, trace_end, max_traces_mem_core)) # Store aligned traces in output project. for align_traces in aligned_traces_total: for aligned_trace in align_traces: - project_out.append_trace(wave = aligned_trace.wave, - plaintext = aligned_trace.plaintext, - ciphertext = aligned_trace.ciphertext, - key = aligned_trace.key) + project_out.append_trace( + wave=aligned_trace.wave, + plaintext=aligned_trace.plaintext, + ciphertext=aligned_trace.ciphertext, + key=aligned_trace.key) num_traces_aligned += 1 project_out.save() # Free memory. @@ -375,11 +388,11 @@ def main(argv=None): if "cwp" in str(args.project_in): type = "cw" logger.info(f"Opening DB {args.project_out}") - project_in_cfg = ProjectConfig(type = type, - path = args.project_in, - wave_dtype = np.uint16, - overwrite = False, - trace_threshold = args.max_traces_mem) + project_in_cfg = ProjectConfig(type=type, + path=args.project_in, + wave_dtype=np.uint16, + overwrite=False, + trace_threshold=args.max_traces_mem) project_in = SCAProject(project_in_cfg) project_in.create_project() @@ -395,11 +408,11 @@ def main(argv=None): # Create output database. logger.info(f"Creating new DB {args.project_out}") - project_out_cfg = ProjectConfig(type = type, - path = args.project_out, - wave_dtype = np.uint16, - overwrite = True, - trace_threshold = args.max_traces_mem) + project_out_cfg = ProjectConfig(type=type, + path=args.project_out, + wave_dtype=np.uint16, + overwrite=True, + trace_threshold=args.max_traces_mem) project_out = SCAProject(project_out_cfg) project_out.create_project() project_out.write_metadata(metadata_in) diff --git a/analysis/tvla.py b/analysis/tvla.py index 6d7c8364..4ff8dbe2 100755 --- a/analysis/tvla.py +++ b/analysis/tvla.py @@ -34,14 +34,16 @@ app = typer.Typer(add_completion=False) - script_dir = Path(__file__).parent.absolute() class UnformattedLog(object): + def __init__(self): self.logger = log.getLogger() - self.formatters = [handler.formatter for handler in self.logger.handlers] + self.formatters = [ + handler.formatter for handler in self.logger.handlers + ] def __enter__(self): for i in range(len(self.formatters)): @@ -194,9 +196,10 @@ def tvla_plotting_fnc( label="trigger high", ) if trigger_low < num_samples: - axs[0].plot( - xaxs[trigger_low:], single_trace[trigger_low:], "grey", label="trigger low" - ) + axs[0].plot(xaxs[trigger_low:], + single_trace[trigger_low:], + "grey", + label="trigger low") axs[0].legend(loc="upper right", prop={"size": 7}) for i_order in range(num_orders): axs[1 + i_order].set_ylabel("t-test " + str(i_order + 1)) @@ -240,31 +243,29 @@ def run_tvla(ctx: typer.Context): log.basicConfig( format=log_format, datefmt="%Y-%m-%d %I:%M:%S", - handlers=[log.FileHandler("tmp/log.txt"), log.StreamHandler()], + handlers=[log.FileHandler("tmp/log.txt"), + log.StreamHandler()], level=log.INFO, force=True, ) - if ( - cfg["mode"] != "kmac" and cfg["mode"] != "aes" and - cfg["mode"] != "sha3" and cfg["mode"] != "otbn" - ): + if (cfg["mode"] != "kmac" and cfg["mode"] != "aes" and + cfg["mode"] != "sha3" and cfg["mode"] != "otbn"): log.info("Unsupported mode:" + cfg["mode"] + ', falling back to "aes"') # Currently, specific TVLA exists only for AES. # Other modes can be tested only using general TVLA. if cfg["mode"] in {"kmac", "otbn", "sha3"}: assert ( - cfg["test_type"] != "SPECIFIC_BYTE" - ), "Specific test not supported for this mode." + cfg["test_type"] + != "SPECIFIC_BYTE"), "Specific test not supported for this mode." assert ( - cfg["test_type"] != "SPECIFIC_BIT" - ), "Specific test not supported for this mode." + cfg["test_type"] + != "SPECIFIC_BIT"), "Specific test not supported for this mode." if cfg["mode"] == "sha3": - assert ( - cfg["test_type"] == "GENERAL_DATA" - ), "SHA3 can only be tested using GENERAL_DATA test type" + assert (cfg["test_type"] == "GENERAL_DATA" + ), "SHA3 can only be tested using GENERAL_DATA test type" if cfg["test_type"] == "GENERAL_DATA": general_test_key = False @@ -347,7 +348,8 @@ def run_tvla(ctx: typer.Context): save_to_disk_trace = cfg["save_to_disk"] save_to_disk_leakage = cfg["save_to_disk"] - save_to_disk_ttest = cfg["save_to_disk_ttest"] and (cfg["ttest_step_file"] is None) + save_to_disk_ttest = cfg["save_to_disk_ttest"] and (cfg["ttest_step_file"] + is None) # Step-wise processing isn't compatible with a couple of other arguments. if num_steps > 1: @@ -384,23 +386,19 @@ def run_tvla(ctx: typer.Context): # Compute statistics. # ttest_trace has dimensions [num_orders, num_rnds, num_data, num_samples]. - ttest_trace = Parallel(n_jobs=num_jobs)( - delayed(compute_statistics)( - cfg["test_type"], - num_orders, - histograms_in[:, :, :, i: i + sample_step_ttest, :], - x_axis, - rnd_list, - byte_list, - bit_list, - ) - for i in range(0, num_samples, sample_step_ttest) - ) + ttest_trace = Parallel(n_jobs=num_jobs)(delayed(compute_statistics)( + cfg["test_type"], + num_orders, + histograms_in[:, :, :, i:i + sample_step_ttest, :], + x_axis, + rnd_list, + byte_list, + bit_list, + ) for i in range(0, num_samples, sample_step_ttest)) ttest_trace = np.concatenate((ttest_trace[:]), axis=3) - if ( - cfg["input_histogram_file"] is None or cfg["output_histogram_file"] is not None - ) and cfg["ttest_step_file"] is None: + if (cfg["input_histogram_file"] is None or cfg["output_histogram_file"] + is not None) and cfg["ttest_step_file"] is None: # Either don't have previously generated histograms or we need to append previously # generated histograms. # Make sure the project file is compatible with the previously generated histograms. @@ -446,15 +444,11 @@ def run_tvla(ctx: typer.Context): num_samples = len(project.get_waves(0)) - sample_start if num_samples + sample_start > len(project.get_waves(0)): - log.warning( - f"Selected sample window {sample_start} to " + - f"{sample_start + num_samples} is out of range!" - ) + log.warning(f"Selected sample window {sample_start} to " + + f"{sample_start + num_samples} is out of range!") num_samples = len(project.get_waves(0)) - sample_start - log.warning( - f"Will use samples from {sample_start} " + - f"to {sample_start + num_samples} instead!" - ) + log.warning(f"Will use samples from {sample_start} " + + f"to {sample_start + num_samples} instead!") # Overall number of traces, trace start and end indices. num_traces_max = metadata.get("num_traces") @@ -486,14 +480,12 @@ def run_tvla(ctx: typer.Context): trace_start_vec.append(trace_start_tot + i_step * num_traces_step) if i_step < num_steps - 1 or num_traces_rem == 0: num_traces_vec.append(num_traces_step) - trace_end_vec.append( - trace_start_vec[i_step] + num_traces_vec[i_step] - 1 - ) + trace_end_vec.append(trace_start_vec[i_step] + + num_traces_vec[i_step] - 1) else: num_traces_vec.append(num_traces_step + num_traces_rem) - trace_end_vec.append( - trace_start_vec[i_step] + num_traces_vec[i_step] - 1 - ) + trace_end_vec.append(trace_start_vec[i_step] + + num_traces_vec[i_step] - 1) # The number of parallel jobs to use for the processing-heavy tasks. num_jobs = multiprocessing.cpu_count() @@ -536,18 +528,20 @@ def run_tvla(ctx: typer.Context): # Converting traces from floating point to integer and creating a dense copy. log.info("Converting Traces") if proj_waves[0].dtype == "uint16": - traces = np.empty((num_traces, num_samples), dtype=np.uint16) + traces = np.empty((num_traces, num_samples), + dtype=np.uint16) log.info( f"Will use samples from {sample_start} to {sample_start + num_samples}" ) for i_trace in range(num_traces): traces[i_trace] = proj_waves[i_trace][ - sample_start: sample_start + num_samples - ] + sample_start:sample_start + num_samples] else: - traces = np.empty((num_traces, num_samples), dtype=np.double) + traces = np.empty((num_traces, num_samples), + dtype=np.double) for i_trace in range(num_traces): - traces[i_trace] = (proj_waves[i_trace] + 0.5) * trace_resolution + traces[i_trace] = (proj_waves[i_trace] + + 0.5) * trace_resolution traces = traces.astype("uint16") # Define upper and lower limits. @@ -565,10 +559,9 @@ def run_tvla(ctx: typer.Context): # Filtering of converted traces (len = num_samples). traces_to_use itself can be # used to index the entire project file (len >= num_samples). traces_to_use = np.zeros(num_traces_max, dtype=bool) - traces_to_use[trace_start: trace_end + 1] = np.all( - (traces >= min_trace) & (traces <= max_trace), axis=1 - ) - traces = traces[traces_to_use[trace_start: trace_end + 1]] + traces_to_use[trace_start:trace_end + 1] = np.all( + (traces >= min_trace) & (traces <= max_trace), axis=1) + traces = traces[traces_to_use[trace_start:trace_end + 1]] if i_step == 0: # Keep a single trace to create the figures. @@ -584,10 +577,8 @@ def run_tvla(ctx: typer.Context): trace_end=trace_end, ) - if ( - (save_to_disk_trace is True or save_to_disk_ttest is True) and - general_test and i_step == 0 - ): + if ((save_to_disk_trace is True or save_to_disk_ttest is True) + and general_test and i_step == 0): np.save("tmp/single_trace.npy", single_trace) else: trace_file = np.load(cfg["trace_file"]) @@ -610,10 +601,8 @@ def run_tvla(ctx: typer.Context): # Correct num_traces based on filtering. num_traces_orig = num_traces num_traces = np.sum(traces_to_use) - log.info( - f"Will use {num_traces} traces " - f"({100 * num_traces / num_traces_orig:.1f}%)" - ) + log.info(f"Will use {num_traces} traces " + f"({100 * num_traces / num_traces_orig:.1f}%)") num_traces_used_total += num_traces @@ -627,10 +616,12 @@ def run_tvla(ctx: typer.Context): # Create local, dense copies of keys and plaintexts. This allows the leakage # computation to be parallelized. if cfg["mode"] == "otbn": - keys = np.empty((num_traces_orig, key_len_bytes), dtype=np.uint8) + keys = np.empty((num_traces_orig, key_len_bytes), + dtype=np.uint8) else: keys = np.empty((num_traces_orig, 16), dtype=np.uint8) - plaintexts = np.empty((num_traces_orig, 16), dtype=np.uint8) + plaintexts = np.empty((num_traces_orig, 16), + dtype=np.uint8) if specific_test: keys[:] = project.get_keys(trace_start, trace_end + 1) @@ -648,47 +639,43 @@ def run_tvla(ctx: typer.Context): if general_test_data: plaintexts_nparrays.append( np.frombuffer( - project.project.textins[i], dtype=np.uint8 - ) - ) + project.project.textins[i], + dtype=np.uint8)) else: keys_nparrays.append( - np.frombuffer( - project.project.keys[i], dtype=np.uint8 - ) - ) + np.frombuffer(project.project.keys[i], + dtype=np.uint8)) # Select the correct slice of keys for each step. if OTTraceLib: if general_test_data: plaintexts[:] = project.get_plaintexts( - trace_start, trace_end + 1 - ) + trace_start, trace_end + 1) else: - keys[:] = project.get_keys(trace_start, trace_end + 1) + keys[:] = project.get_keys(trace_start, + trace_end + 1) else: if general_test_data: plaintexts[:] = plaintexts_nparrays[ - trace_start: trace_end + 1 - ] + trace_start:trace_end + 1] else: - keys[:] = keys_nparrays[trace_start: trace_end + 1] + keys[:] = keys_nparrays[trace_start:trace_end + 1] # Only select traces to use. if general_test_key: - keys = keys[traces_to_use[trace_start: trace_end + 1]] + keys = keys[traces_to_use[trace_start:trace_end + 1]] if general_test_data: - plaintexts = plaintexts[traces_to_use[trace_start: trace_end + 1]] + plaintexts = plaintexts[ + traces_to_use[trace_start:trace_end + 1]] if specific_test: - keys = keys[traces_to_use[trace_start: trace_end + 1]] + keys = keys[traces_to_use[trace_start:trace_end + 1]] if OTTraceLib: - plaintexts = project.get_plaintexts(trace_start, trace_end + 1) + plaintexts = project.get_plaintexts( + trace_start, trace_end + 1) else: plaintexts[:] = project.project.textins[ - trace_start: trace_end + 1 - ] + trace_start:trace_end + 1] plaintexts = plaintexts[ - traces_to_use[trace_start: trace_end + 1] - ] + traces_to_use[trace_start:trace_end + 1]] # We don't need the project file anymore after this point. Close it # together with all trace files opened in the background. @@ -703,19 +690,17 @@ def run_tvla(ctx: typer.Context): if specific_test_byte: leakage = Parallel(n_jobs=num_jobs)( delayed(compute_leakage_aes_byte)( - keys[i: i + trace_step_leakage], - plaintexts[i: i + trace_step_leakage], + keys[i:i + trace_step_leakage], + plaintexts[i:i + trace_step_leakage], ) - for i in range(0, num_traces, trace_step_leakage) - ) + for i in range(0, num_traces, trace_step_leakage)) else: leakage = Parallel(n_jobs=num_jobs)( delayed(compute_leakage_aes_bit)( - keys[i: i + trace_step_leakage], - plaintexts[i: i + trace_step_leakage], + keys[i:i + trace_step_leakage], + plaintexts[i:i + trace_step_leakage], ) - for i in range(0, num_traces, trace_step_leakage) - ) + for i in range(0, num_traces, trace_step_leakage)) leakage = np.concatenate((leakage[:]), axis=2) if save_to_disk_leakage: log.info("Saving Leakage") @@ -726,14 +711,14 @@ def run_tvla(ctx: typer.Context): elif general_test_key: log.info("Computing Leakage") # We identify the fixed key by looking at the first 20 keys in the project. - leakage = compute_leakage_general(keys, find_fixed_entry(keys[0:20])) + leakage = compute_leakage_general(keys, + find_fixed_entry(keys[0:20])) else: assert general_test_data log.info("Computing Leakage") # We identify the fixed data by looking at the first 20 plaintexts in the project. leakage = compute_leakage_general( - plaintexts, find_fixed_entry(plaintexts[0:20]) - ) + plaintexts, find_fixed_entry(plaintexts[0:20])) # Uncomment the function call below for debugging e.g. when the t-test results aren't # centered around 0. @@ -757,11 +742,9 @@ def run_tvla(ctx: typer.Context): trace_resolution, rnd_list, byte_list, - traces[:, i: i + sample_step_hist], + traces[:, i:i + sample_step_hist], leakage, - ) - for i in range(0, num_samples, sample_step_hist) - ) + ) for i in range(0, num_samples, sample_step_hist)) histograms = np.concatenate((histograms[:]), axis=3) elif specific_test_bit: histograms = Parallel(n_jobs=num_jobs)( @@ -769,11 +752,9 @@ def run_tvla(ctx: typer.Context): trace_resolution, rnd_list, bit_list, - traces[:, i: i + sample_step_hist], + traces[:, i:i + sample_step_hist], leakage, - ) - for i in range(0, num_samples, sample_step_hist) - ) + ) for i in range(0, num_samples, sample_step_hist)) histograms = np.concatenate((histograms[:]), axis=3) else: # For every time sample we make 2 histograms, one for the fixed set and one for the @@ -784,11 +765,10 @@ def run_tvla(ctx: typer.Context): # v and w indices are not used but we keep them for code compatiblitly with # non-general AES TVLA. histograms = Parallel(n_jobs=num_jobs)( - delayed(compute_histograms_general)( - trace_resolution, traces[:, i: i + sample_step_hist], leakage - ) - for i in range(0, num_samples, sample_step_hist) - ) + delayed(compute_histograms_general) + (trace_resolution, traces[:, + i:i + sample_step_hist], leakage) + for i in range(0, num_samples, sample_step_hist)) histograms = np.concatenate((histograms[:]), axis=3) # Free traces from memory as they are not needed anymore. @@ -828,14 +808,12 @@ def run_tvla(ctx: typer.Context): delayed(compute_statistics)( cfg["test_type"], num_orders, - histograms[:, :, :, i: i + sample_step_ttest, :], + histograms[:, :, :, i:i + sample_step_ttest, :], x_axis, rnd_list, byte_list, bit_list, - ) - for i in range(0, num_samples, sample_step_ttest) - ) + ) for i in range(0, num_samples, sample_step_ttest)) ttest_trace = np.concatenate((ttest_trace[:]), axis=3) # Building the t-test statistics vs. number of traces used. ttest_step has dimensions @@ -844,8 +822,7 @@ def run_tvla(ctx: typer.Context): log.info("Updating T-test Statistics vs. Number of Traces") if i_step == 0: ttest_step = np.empty( - (num_orders, num_rnds, num_data, num_samples, num_steps) - ) + (num_orders, num_rnds, num_data, num_samples, num_steps)) ttest_step[:, :, :, :, i_step] = ttest_trace rnd_ext = list(range(num_rnds)) @@ -872,14 +849,12 @@ def run_tvla(ctx: typer.Context): byte_ext = np.zeros((num_bytes), dtype=np.uint8) for i_rnd in range(num_rnds): assert rnd_list[i_rnd] in ttest_step_file["rnd_list"] - rnd_ext[i_rnd] = np.where(ttest_step_file["rnd_list"] == rnd_list[i_rnd])[ - 0 - ][0] + rnd_ext[i_rnd] = np.where( + ttest_step_file["rnd_list"] == rnd_list[i_rnd])[0][0] for i_byte in range(num_bytes): assert byte_list[i_byte] in ttest_step_file["byte_list"] byte_ext[i_byte] = np.where( - ttest_step_file["byte_list"] == byte_list[i_byte] - )[0][0] + ttest_step_file["byte_list"] == byte_list[i_byte])[0][0] # Plot the t-test vs. time figures for the maximum number of traces. ttest_trace = ttest_step[:, :, :, :, num_steps - 1] @@ -923,13 +898,11 @@ def run_tvla(ctx: typer.Context): if np.any(failure): log.info( "Leakage above threshold identified in the following order(s), round(s) " - "and byte(s) marked with X:" - ) + "and byte(s) marked with X:") if np.any(nan): log.info( "Couldn't compute statistics for order(s), round(s) and byte(s) marked " - "with O:" - ) + "with O:") with UnformattedLog(): byte_str = "Byte |" dash_str = "----------" @@ -942,11 +915,14 @@ def run_tvla(ctx: typer.Context): log.info(f"{byte_str}") log.info(f"{dash_str}") for i_rnd in range(num_rnds): - result_str = "Round " + str(rnd_list[i_rnd]).rjust(2) + " |" + result_str = "Round " + str( + rnd_list[i_rnd]).rjust(2) + " |" for i_byte in range(num_bytes): - if failure[i_order, rnd_ext[i_rnd], byte_ext[i_byte]]: + if failure[i_order, rnd_ext[i_rnd], + byte_ext[i_byte]]: result_str += str("X").rjust(5) - elif nan[i_order, rnd_ext[i_rnd], byte_ext[i_byte]]: + elif nan[i_order, rnd_ext[i_rnd], + byte_ext[i_byte]]: result_str += str("O").rjust(5) else: result_str += " " @@ -956,13 +932,11 @@ def run_tvla(ctx: typer.Context): if np.any(failure): log.info( "Leakage above threshold identified in the following order(s), round(s) " - "and bit(s) marked with X:" - ) + "and bit(s) marked with X:") if np.any(nan): log.info( "Couldn't compute statistics for order(s), round(s) and bit(s) marked " - "with O:" - ) + "with O:") with UnformattedLog(): bit_str = "Bit |" dash_str = "----------" @@ -975,9 +949,11 @@ def run_tvla(ctx: typer.Context): log.info(f"{bit_str}") log.info(f"{dash_str}") for i_rnd in range(num_rnds): - result_str = "Round " + str(rnd_list[i_rnd]).rjust(2) + " |" + result_str = "Round " + str( + rnd_list[i_rnd]).rjust(2) + " |" for i_bit in range(num_bits): - if failure[i_order, rnd_ext[i_rnd], bit_ext[i_bit]]: + if failure[i_order, rnd_ext[i_rnd], + bit_ext[i_bit]]: result_str += str("X").rjust(5) elif nan[i_order, rnd_ext[i_rnd], bit_ext[i_bit]]: result_str += str("O").rjust(5) @@ -990,7 +966,8 @@ def run_tvla(ctx: typer.Context): "Leakage above threshold identified in the following order(s) marked with X" ) if np.any(nan): - log.info("Couldn't compute statistics for order(s) marked with O:") + log.info( + "Couldn't compute statistics for order(s) marked with O:") with UnformattedLog(): for i_order in range(num_orders): result_str = "Order " + str(i_order + 1) + ": " @@ -1012,12 +989,8 @@ def run_tvla(ctx: typer.Context): # Catch case where certain metadata isn't saved to project file (e.g. older measurement) try: sampling_rate = float(metadata["sampling_rate"]) / 1e6 - textbox = ( - textbox + - "Sample rate:\n" + - str(math.floor(sampling_rate)) + - " MS/s\n\n" - ) + textbox = (textbox + "Sample rate:\n" + + str(math.floor(sampling_rate)) + " MS/s\n\n") except KeyError: textbox = textbox try: @@ -1025,19 +998,23 @@ def run_tvla(ctx: typer.Context): except KeyError: textbox = textbox try: - textbox = textbox + "Samples:\n" + str(metadata["num_samples"]) + "\n\n" + textbox = textbox + "Samples:\n" + str( + metadata["num_samples"]) + "\n\n" except KeyError: textbox = textbox try: - textbox = textbox + "Offset:\n" + str(metadata["offset_samples"]) + "\n\n" + textbox = textbox + "Offset:\n" + str( + metadata["offset_samples"]) + "\n\n" except KeyError: textbox = textbox try: - textbox = textbox + "Scope gain:\n" + str(metadata["scope_gain"]) + "\n\n" + textbox = textbox + "Scope gain:\n" + str( + metadata["scope_gain"]) + "\n\n" except KeyError: textbox = textbox try: - textbox = textbox + "Traces:\n" + str(num_traces_used_total) + "\n\n" + textbox = textbox + "Traces:\n" + str( + num_traces_used_total) + "\n\n" except KeyError: textbox = textbox if textbox != "": @@ -1066,25 +1043,18 @@ def run_tvla(ctx: typer.Context): metadata, ) if specific_test_byte: - title = ( - "TVLA of " + - "aes_t_test_round_" + - str(rnd_list[i_rnd]) + - "_byte_" + - str(byte_list[i_data]) - ) + title = ("TVLA of " + "aes_t_test_round_" + + str(rnd_list[i_rnd]) + "_byte_" + + str(byte_list[i_data])) elif specific_test_bit: - title = ( - "TVLA of " + - "aes_t_test_round_" + - str(rnd_list[i_rnd]) + - "_bit_" + - str(bit_list[i_data]) - ) + title = ("TVLA of " + "aes_t_test_round_" + + str(rnd_list[i_rnd]) + "_bit_" + + str(bit_list[i_data])) # Catch case where datetime data isn't saved # to project file (e.g. older measurement) try: - title = title + "\n" + "Captured: " + metadata["datetime"] + title = title + "\n" + "Captured: " + metadata[ + "datetime"] except KeyError: title = title axs[0].set_title(title) @@ -1102,7 +1072,9 @@ def run_tvla(ctx: typer.Context): fontsize=9, horizontalalignment="center", verticalalignment="center", - bbox=dict(boxstyle="round", facecolor="w", linewidth=0.6), + bbox=dict(boxstyle="round", + facecolor="w", + linewidth=0.6), ) plt.subplots_adjust(right=0.84) plt.xlabel("time [samples]") @@ -1173,7 +1145,9 @@ def run_tvla(ctx: typer.Context): # Determine resolution. xres_vec = [10e6, 1e6, 100e3, 10e3, 1e3, 100, 10, 1] for xres in xres_vec: - if int(np.around((trace_end_vec[0] - trace_start_vec[0]) / xres)) > 0: + if int( + np.around((trace_end_vec[0] - trace_start_vec[0]) / + xres)) > 0: if xres >= 1e6: xres_label = str(int(xres / 1e6)) + "M" elif xres >= 1e3: @@ -1182,15 +1156,17 @@ def run_tvla(ctx: typer.Context): xres_label = str(int(xres)) break - xticks = [np.around(trace_end / xres) for trace_end in trace_end_vec] + xticks = [ + np.around(trace_end / xres) for trace_end in trace_end_vec + ] xticklabels = [str(int(tick)) for tick in xticks] # Empty every second label if we got more than 10 steps. if num_steps > 10: for i_step in range(num_steps): - xticklabels[i_step] = ( - "" if (i_step % 2 == 0) else xticklabels[i_step] - ) + xticklabels[i_step] = ("" if + (i_step % + 2 == 0) else xticklabels[i_step]) for i_rnd in range(num_rnds): @@ -1213,7 +1189,8 @@ def run_tvla(ctx: typer.Context): half_window = samples_per_rnd // 2 + 40 samples = { - "aes": range( + "aes": + range( max( rnd_offset + (rnd_list[i_rnd] * samples_per_rnd) - @@ -1231,62 +1208,50 @@ def run_tvla(ctx: typer.Context): else: # Even if cfg["sample_start"] is specified and cfg["num_samples"] is not, # num_samples is set to a valid value and the code below has valid inputs. - range_specified = ( - True - if ( - ("sample_start" in cfg and cfg["sample_start"] is not None) or - ("num_samples" in cfg and cfg["num_samples"] is not None) - ) - else False - ) + range_specified = (True if + (("sample_start" in cfg and + cfg["sample_start"] is not None) or + ("num_samples" in cfg and + cfg["num_samples"] is not None)) else + False) samples = { # Simply plot all samples within the selected range. - "aes": range(0, num_samples), + "aes": + range(0, num_samples), # Plot samples within key absorption phase, unless a range is specified. - "kmac": ( - range(0, num_samples) - if range_specified - else range(520, 2460) - ), + "kmac": (range(0, num_samples) + if range_specified else range(520, 2460)), # Plot samples within actual SHA3 phase, unless a range is specified. - "sha3": ( - range(0, num_samples) - if range_specified - else range(1150, 3150) - ), + "sha3": (range(0, num_samples) + if range_specified else range(1150, 3150)), # Simply plot all samples within the selected range. - "otbn": range(0, num_samples), + "otbn": + range(0, num_samples), } for i_order in range(num_orders): for i_byte in range(num_bytes): for i_sample in samples[cfg["mode"]]: axs[i_order].plot( - ttest_step[ - i_order, rnd_ext[i_rnd], byte_ext[i_byte], i_sample - ], + ttest_step[i_order, rnd_ext[i_rnd], + byte_ext[i_byte], i_sample], "k", ) axs[i_order].plot(c * threshold, "r") axs[i_order].plot(-threshold * c, "r") axs[i_order].set_xlabel( - str("number of traces [" + xres_label + "]") - ) + str("number of traces [" + xres_label + "]")) axs[i_order].set_xticks(range(num_steps)) axs[i_order].set_xticklabels(xticklabels) axs[i_order].set_ylabel( - "t-test " + - str(i_order + 1) + + "t-test " + str(i_order + 1) + "\nfor samples " + - str(samples[cfg["mode"]][0]) + - " to " + - str(samples[cfg["mode"]][-1]) - ) + str(samples[cfg["mode"]][0]) + " to " + + str(samples[cfg["mode"]][-1])) - filename = ( - cfg["mode"] + "_t_test_steps_round_" + str(rnd_list[i_rnd]) + ".png" - ) + filename = (cfg["mode"] + "_t_test_steps_round_" + + str(rnd_list[i_rnd]) + ".png") plt.savefig("tmp/figures/" + filename) if num_rnds == 1: plt.show() @@ -1315,142 +1280,110 @@ def run_tvla(ctx: typer.Context): default_filter_traces = True default_update_cfg_file = False - # Help messages of the options -help_cfg_file = inspect.cleandoc( - """Configuration file. Default: """ + str(default_cfg_file) -) +help_cfg_file = inspect.cleandoc("""Configuration file. Default: """ + + str(default_cfg_file)) help_project_file = inspect.cleandoc( """Name of the ChipWhisperer project file to use. Default: - """ + - str(default_project_file) -) + """ + str(default_project_file)) help_trace_file = inspect.cleandoc( """Name of the trace file containing the numpy array with all traces in 16-bit integer format. If not provided, the data from the ChipWhisperer project file is used. Ignored for number-of-steps > 1. Default: """ + - str(default_trace_file) -) + str(default_trace_file)) help_trace_start = inspect.cleandoc( """Index of the first trace to use. If not provided, starts at - the first trace. Default: """ + - str(default_trace_start) -) + the first trace. Default: """ + str(default_trace_start)) help_trace_end = inspect.cleandoc( """Index of the last trace to use. If not provided, ends at the - last trace. Default: """ + - str(default_trace_end) -) + last trace. Default: """ + str(default_trace_end)) help_leakage_file = inspect.cleandoc( """Name of the leakage file containing the numpy array with the leakage model for all rounds, all bytes, and all traces. If not provided, the leakage is computed from the data in the ChipWhisperer project file. Ignored for number-of-steps > 1. - Default: """ + - str(default_leakage_file) -) + Default: """ + str(default_leakage_file)) help_save_to_disk = inspect.cleandoc( """Save trace and leakage files to disk. Ignored when - number-of-steps > 1. Default: """ + - str(default_save_to_disk) -) + number-of-steps > 1. Default: """ + str(default_save_to_disk)) help_save_to_disk_ttest = inspect.cleandoc( """Save t-test files to disk. Ignored when ttset-step-file is not None. Default: """ + - str(default_save_to_disk_ttest) -) + str(default_save_to_disk_ttest)) help_round_select = inspect.cleandoc( """Index of the AES round for which the histograms are to be computed: 0-10. If not provided, the histograms for all AES rounds are computed. To select multiple but not all rounds, specify the argument once per selected round, e.g., "--round-select 0 --round-select 1". Default: """ + - str(default_round_select) -) + str(default_round_select)) help_byte_select = inspect.cleandoc( """Index of the AES state byte for which the histograms are to be computed: 0-15. If not provided, the histograms for all AES state bytes are computed. To select multiple but not all bytes, specify the argument once per selected byte, e.g., - "--byte-select 0 --byte-select 1". Default: """ + - str(default_byte_select) -) + "--byte-select 0 --byte-select 1". Default: """ + str(default_byte_select)) help_input_histogram_file = inspect.cleandoc( """Name of the input file containing the histograms. Not required. If both -input_histogram_file and -output_histogram_file are provided, the input file is appended with more data to produce the output file. - Default: """ + - str(default_input_histogram_file) -) + Default: """ + str(default_input_histogram_file)) help_output_histogram_file = inspect.cleandoc( """Name of the output file to store generated histograms. Not required. If both -input_histogram_file and -output_histogram_file are provided, the input file is appended with more data to produce the output file. - Default: """ + - str(default_output_histogram_file) -) + Default: """ + str(default_output_histogram_file)) help_number_of_steps = inspect.cleandoc( """Number of steps to breakdown the analysis into. For every step, traces are separately filtered and the leakage is computed. The histograms are appended to the ones of the previous step. This is useful when operating on very large trace sets and/or when analyzing how results change with the number of traces used. Default: - """ + - str(default_number_of_steps) -) + """ + str(default_number_of_steps)) help_ttest_step_file = inspect.cleandoc( """Name of the t-test step file containing one t-test analysis per step. If not provided, the data is recomputed. Default: - """ + - str(default_ttest_step_file) -) + """ + str(default_ttest_step_file)) help_plot_figures = inspect.cleandoc( """Plot figures and save them to disk. Default: - """ + - str(default_plot_figures) -) + """ + str(default_plot_figures)) help_test_type = inspect.cleandoc( """Select test type: can be either "SPECIFIC_BYTE", "GENERA_KEY", or "GENERAL_DATA". - Default: """ + - str(default_test_type) -) + Default: """ + str(default_test_type)) help_mode = inspect.cleandoc( """Select mode: can be either "aes", "kmac", "sha3" or "otbn". - Default: """ + - str(default_mode) -) + Default: """ + str(default_mode)) help_filter_traces = inspect.cleandoc( """Excludes the outlier traces from the analysis. A trace is an outlier if any of the points is more than 3.5 sigma away from the mean. - Default: """ + - str(default_filter_traces) -) + Default: """ + str(default_filter_traces)) help_update_cfg_file = inspect.cleandoc( """Update existing configuration file or create if there - isn't any configuration file. Default: """ + - str(default_update_cfg_file) -) + isn't any configuration file. Default: """ + str(default_update_cfg_file)) @app.callback() def main( - ctx: typer.Context, - cfg_file: str = typer.Option(None, help=help_cfg_file), - project_file: str = typer.Option(None, help=help_project_file), - trace_file: str = typer.Option(None, help=help_trace_file), - trace_start: int = typer.Option(None, help=help_trace_start), - trace_end: int = typer.Option(None, help=help_trace_end), - leakage_file: str = typer.Option(None, help=help_leakage_file), - save_to_disk: bool = typer.Option(None, help=help_save_to_disk), - save_to_disk_ttest: bool = typer.Option(None, help=help_save_to_disk_ttest), - round_select: list[int] = typer.Option(None, help=help_round_select), - byte_select: list[int] = typer.Option(None, help=help_byte_select), - input_histogram_file: str = typer.Option(None, help=help_input_histogram_file), - output_histogram_file: str = typer.Option(None, help=help_output_histogram_file), - number_of_steps: int = typer.Option(None, help=help_number_of_steps), - ttest_step_file: str = typer.Option(None, help=help_ttest_step_file), - plot_figures: bool = typer.Option(None, help=help_plot_figures), - test_type: str = typer.Option(None, help=help_test_type), - mode: str = typer.Option(None, help=help_mode), - filter_traces: bool = typer.Option(None, help=help_filter_traces), - update_cfg_file: bool = typer.Option(None, help=help_update_cfg_file), + ctx: typer.Context, + cfg_file: str = typer.Option(None, help=help_cfg_file), + project_file: str = typer.Option(None, help=help_project_file), + trace_file: str = typer.Option(None, help=help_trace_file), + trace_start: int = typer.Option(None, help=help_trace_start), + trace_end: int = typer.Option(None, help=help_trace_end), + leakage_file: str = typer.Option(None, help=help_leakage_file), + save_to_disk: bool = typer.Option(None, help=help_save_to_disk), + save_to_disk_ttest: bool = typer.Option(None, + help=help_save_to_disk_ttest), + round_select: list[int] = typer.Option(None, help=help_round_select), + byte_select: list[int] = typer.Option(None, help=help_byte_select), + input_histogram_file: str = typer.Option( + None, help=help_input_histogram_file), + output_histogram_file: str = typer.Option( + None, help=help_output_histogram_file), + number_of_steps: int = typer.Option(None, help=help_number_of_steps), + ttest_step_file: str = typer.Option(None, help=help_ttest_step_file), + plot_figures: bool = typer.Option(None, help=help_plot_figures), + test_type: str = typer.Option(None, help=help_test_type), + mode: str = typer.Option(None, help=help_mode), + filter_traces: bool = typer.Option(None, help=help_filter_traces), + update_cfg_file: bool = typer.Option(None, help=help_update_cfg_file), ): """A histogram-based TVLA described in "Fast Leakage Assessment" by O. Reparaz, B. Gierlichs and I. Verbauwhede (https://eprint.iacr.org/2017/624.pdf).""" @@ -1459,23 +1392,23 @@ def main( # Assign default values to the options. for v in [ - "project_file", - "trace_file", - "trace_start", - "trace_end", - "leakage_file", - "save_to_disk", - "save_to_disk_ttest", - "round_select", - "byte_select", - "input_histogram_file", - "output_histogram_file", - "number_of_steps", - "ttest_step_file", - "plot_figures", - "test_type", - "mode", - "filter_traces", + "project_file", + "trace_file", + "trace_start", + "trace_end", + "leakage_file", + "save_to_disk", + "save_to_disk_ttest", + "round_select", + "byte_select", + "input_histogram_file", + "output_histogram_file", + "number_of_steps", + "ttest_step_file", + "plot_figures", + "test_type", + "mode", + "filter_traces", ]: run_cmd = f"""cfg[v] = default_{v}""" exec(run_cmd) @@ -1488,21 +1421,21 @@ def main( # Overwrite options from CLI, if provided. for v in [ - "project_file", - "trace_file", - "trace_start", - "trace_end", - "leakage_file", - "save_to_disk", - "save_to_disk_ttest", - "input_histogram_file", - "output_histogram_file", - "number_of_steps", - "ttest_step_file", - "plot_figures", - "test_type", - "mode", - "filter_traces", + "project_file", + "trace_file", + "trace_start", + "trace_end", + "leakage_file", + "save_to_disk", + "save_to_disk_ttest", + "input_histogram_file", + "output_histogram_file", + "number_of_steps", + "ttest_step_file", + "plot_figures", + "test_type", + "mode", + "filter_traces", ]: run_cmd = f"""if {v} is not None: cfg[v] = {v}""" exec(run_cmd) diff --git a/capture/capture_aes.py b/capture/capture_aes.py index 336ca437..4d04d663 100755 --- a/capture/capture_aes.py +++ b/capture/capture_aes.py @@ -2,6 +2,16 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 +"""AES SCA capture script. + +Captures power traces during AES operations. + +The data format of the crypto material (ciphertext, plaintext, and key) inside +the script is stored in plain integer arrays. + +Typical usage: +>>> ./capture_aes.py -c configs/aes_sca_cw310.yaml -p projects/aes_sca_capture +""" import json import logging @@ -33,18 +43,6 @@ plaintext_len = 16 key_len = 16 -"""AES SCA capture script. - -Captures power traces during AES operations. - -The data format of the crypto material (ciphertext, plaintext, and key) inside -the script is stored in plain integer arrays. - -Typical usage: ->>> ./capture_aes.py -c configs/aes_sca_cw310.yaml -p projects/aes_sca_capture -""" - - logger = logging.getLogger() @@ -85,9 +83,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Create target config & setup target. logger.info(f"Initializing target {cfg['target']['target_type']} ...") @@ -101,7 +98,7 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["husky"].get("usb_serial"), + husky_serial=cfg["husky"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) @@ -115,16 +112,16 @@ def setup(cfg: dict, project: Path): if scope_type != "none": # Will determine sampling rate (for Husky only), if not given in cfg. - cfg[scope_type]["sampling_rate"] = determine_sampling_rate(cfg, scope_type) + cfg[scope_type]["sampling_rate"] = determine_sampling_rate( + cfg, scope_type) # Will convert number of cycles into number of samples if they are not given in cfg. cfg[scope_type]["num_samples"] = convert_num_cycles(cfg, scope_type) # Will convert offset in cycles into offset in samples, if they are not given in cfg. - cfg[scope_type]["offset_samples"] = convert_offset_cycles(cfg, scope_type) + cfg[scope_type]["offset_samples"] = convert_offset_cycles( + cfg, scope_type) - logger.info( - f"Initializing scope {scope_type} with a sampling rate of \ - {cfg[scope_type]['sampling_rate']}..." - ) # noqa: E501 + logger.info(f"Initializing scope {scope_type} with a sampling rate of \ + {cfg[scope_type]['sampling_rate']}...") # noqa: E501 # Determine if we are in batch mode or not. batch = True @@ -213,8 +210,8 @@ def configure_cipher(cfg, ot_aes, ot_prng): fpga_mode_bit = 1 # Initialize AES on the target. device_id, owner_page, boot_log, boot_measurements, version = ot_aes.init( - fpga_mode_bit, cfg["test"]["core_config"], cfg["test"]["sensor_config"] - ) + fpga_mode_bit, cfg["test"]["core_config"], + cfg["test"]["sensor_config"]) # Configure PRNGs. # Seed the software LFSR used for initial key masking and additionally # turning off the masking when '0'. @@ -229,9 +226,8 @@ def configure_cipher(cfg, ot_aes, ot_prng): return device_id, owner_page, boot_log, boot_measurements, version -def generate_ref_crypto( - sample_fixed, mode, fixed_key, fixed_plaintext, last_ciphertext -): +def generate_ref_crypto(sample_fixed, mode, fixed_key, fixed_plaintext, + last_ciphertext): """Generate cipher material for the encryption. Args: @@ -253,17 +249,23 @@ def generate_ref_crypto( batch_key = fixed_key else: batch_key = [random.randint(0, 255) for _ in range(key_len)] - batch_plaintext = [random.randint(0, 255) for _ in range(plaintext_len)] + batch_plaintext = [ + random.randint(0, 255) for _ in range(plaintext_len) + ] new_sample_fixed = random.randint(0, 255) & 0x1 elif mode == "aes_fvsr_data": if sample_fixed == 1: batch_plaintext = fixed_plaintext else: - batch_plaintext = [random.randint(0, 255) for _ in range(plaintext_len)] + batch_plaintext = [ + random.randint(0, 255) for _ in range(plaintext_len) + ] new_sample_fixed = random.randint(0, 255) & 0x1 batch_key = fixed_key elif mode == "aes_random": - batch_plaintext = [random.randint(0, 255) for _ in range(plaintext_len)] + batch_plaintext = [ + random.randint(0, 255) for _ in range(plaintext_len) + ] batch_key = fixed_key new_sample_fixed = 1 elif mode == "daisy_chain": @@ -302,8 +304,7 @@ def check_ciphertext(target, expected_last_ciphertext): assert actual_last_ciphertext == expected_last_ciphertext, ( f"Incorrect encryption result!\n" f"actual: {actual_last_ciphertext}\n" - f"expected: {expected_last_ciphertext}" - ) + f"expected: {expected_last_ciphertext}") def capture( @@ -345,9 +346,10 @@ def capture( signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project)) # Main capture with progress bar. remaining_num_traces = capture_cfg.num_traces - with tqdm( - total=remaining_num_traces, desc="Capturing", ncols=80, unit=" traces" - ) as pbar: + with tqdm(total=remaining_num_traces, + desc="Capturing", + ncols=80, + unit=" traces") as pbar: while remaining_num_traces > 0: # Arm the scope. if scope is not None: @@ -357,13 +359,15 @@ def capture( ot_aes.single_encrypt(key_fixed, text_fixed) elif capture_cfg.capture_mode == "daisy_chain": text = ciphertext - ot_aes.batch_daisy_chain(capture_cfg.num_segments, key_fixed, text) + ot_aes.batch_daisy_chain(capture_cfg.num_segments, key_fixed, + text) elif capture_cfg.capture_mode == "aes_random": ot_aes.batch_random(capture_cfg.num_segments, key_fixed) elif capture_cfg.capture_mode == "aes_fvsr_key": ot_aes.batch_fvsr_key(capture_cfg.num_segments, key_fixed) elif capture_cfg.capture_mode == "aes_fvsr_data": - ot_aes.batch_fvsr_data(capture_cfg.num_segments, key_fixed, text_fixed) + ot_aes.batch_fvsr_data(capture_cfg.num_segments, key_fixed, + text_fixed) else: logger.info("Error: Mode not recognized.") return @@ -400,7 +404,8 @@ def capture( if scope is not None: # Memory allocation optimization for CW trace library. - num_segments_storage = project.optimize_capture(num_segments_storage) + num_segments_storage = project.optimize_capture( + num_segments_storage) # Update the loop variable and the progress bar. remaining_num_traces -= capture_cfg.num_segments @@ -417,7 +422,8 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: config: The capture configuration. file: The output file path. """ - if config["capture"]["show_plot"] and config["capture"]["scope_select"] != "none": + if config["capture"]["show_plot"] and config["capture"][ + "scope_select"] != "none": plot.save_plot_to_file( project.get_waves(0, config["capture"]["plot_traces"]), set_indices=None, @@ -427,8 +433,7 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: ) logger.info( f'Created plot with {config["capture"]["plot_traces"]} traces: ' - f'{Path(str(file) + ".html").resolve()}' - ) + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -466,8 +471,7 @@ def main(argv=None): # Configure cipher. device_id, owner_page, boot_log, boot_measurements, version = configure_cipher( - cfg, ot_aes, ot_prng - ) + cfg, ot_aes, ot_prng) # Configure trigger source. # 0 for HW, 1 for SW. @@ -502,17 +506,16 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) if args.save_binary: - metadata["fw_bin"] = helpers.get_binary_blob(cfg["target"]["fw_bin"]) + metadata["fw_bin"] = helpers.get_binary_blob( + cfg["target"]["fw_bin"]) # Store user provided notes. metadata["notes"] = args.notes # Store the Git hash. diff --git a/capture/capture_hmac.py b/capture/capture_hmac.py index 52c5f755..4dc02f4f 100755 --- a/capture/capture_hmac.py +++ b/capture/capture_hmac.py @@ -6,6 +6,16 @@ # Note: The word ciphertext refers to the tag in hmac # To be compatible to the other capture scripts, the variable is # called ciphertext +"""HMAC SCA capture script. + +Captures power traces during HMAC operations. + +The data format of the crypto material (ciphertext, plaintext, and key) inside +the script is stored in plain integer arrays. + +Typical usage: +>>> ./capture_hmac.py -c configs/hmac_sca_cw310.yaml -p projects/hmac_sca_capture +""" import json import logging @@ -32,17 +42,6 @@ from target.targets import Target, TargetConfig from util import check_version, plot -"""HMAC SCA capture script. - -Captures power traces during HMAC operations. - -The data format of the crypto material (ciphertext, plaintext, and key) inside -the script is stored in plain integer arrays. - -Typical usage: ->>> ./capture_hmac.py -c configs/hmac_sca_cw310.yaml -p projects/hmac_sca_capture -""" - # Byte lengths of the text, key, and tag. text_length = 16 key_length = 32 @@ -89,9 +88,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Init scope. scope_type = cfg["capture"]["scope_select"] @@ -112,23 +110,23 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["husky"].get("usb_serial"), + husky_serial=cfg["husky"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) if scope_type != "none": # Determine sampling rate, if necessary. - cfg[scope_type]["sampling_rate"] = determine_sampling_rate(cfg, scope_type) + cfg[scope_type]["sampling_rate"] = determine_sampling_rate( + cfg, scope_type) # Convert number of cycles into number of samples, if necessary. cfg[scope_type]["num_samples"] = convert_num_cycles(cfg, scope_type) # Convert offset in cycles into offset in samples, if necessary. - cfg[scope_type]["offset_samples"] = convert_offset_cycles(cfg, scope_type) + cfg[scope_type]["offset_samples"] = convert_offset_cycles( + cfg, scope_type) - logger.info( - f"Initializing scope {scope_type} with a sampling rate of \ - {cfg[scope_type]['sampling_rate']}..." - ) # noqa: E501 + logger.info(f"Initializing scope {scope_type} with a sampling rate of \ + {cfg[scope_type]['sampling_rate']}...") # noqa: E501 # Determine if we are in batch mode or not. batch = True @@ -208,8 +206,7 @@ def configure_cipher(cfg, ot_hmac, ot_prng): """ # Initialize HMAC on the target. device_id, owner_page, boot_log, boot_measurements, version = ot_hmac.init( - cfg["test"]["core_config"], cfg["test"]["sensor_config"] - ) + cfg["test"]["core_config"], cfg["test"]["sensor_config"]) # Seed the PRNG used for generating keys and plaintexts in batch mode. # Seed host's PRNG. @@ -287,8 +284,7 @@ def check_tag(target, expected_last_tag): assert actual_last_tag == expected_last_tag, ( f"Incorrect encryption result!\n" f"actual: {actual_last_tag}\n" - f"expected: {expected_last_tag}" - ) + f"expected: {expected_last_tag}") def capture( @@ -332,9 +328,10 @@ def capture( signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project)) # Main capture with progress bar. remaining_num_traces = capture_cfg.num_traces - with tqdm( - total=remaining_num_traces, desc="Capturing", ncols=80, unit=" traces" - ) as pbar: + with tqdm(total=remaining_num_traces, + desc="Capturing", + ncols=80, + unit=" traces") as pbar: while remaining_num_traces > 0: # Arm the scope. if scope is not None: @@ -345,10 +342,12 @@ def capture( elif capture_cfg.capture_mode == "random": ot_hmac.random_batch(capture_cfg.num_segments, trigger) elif capture_cfg.capture_mode == "data_fvsr": - ot_hmac.fvsr_batch(key_fixed, capture_cfg.num_segments, trigger) + ot_hmac.fvsr_batch(key_fixed, capture_cfg.num_segments, + trigger) elif capture_cfg.capture_mode == "daisy_chain": text = tag[:text_length] - ot_hmac.daisy_chain(text, key_fixed, capture_cfg.num_segments, trigger) + ot_hmac.daisy_chain(text, key_fixed, capture_cfg.num_segments, + trigger) else: logger.info("Error: Mode not recognized.") return @@ -386,7 +385,8 @@ def capture( if scope is not None: # Memory allocation optimization for CW trace library. - num_segments_storage = project.optimize_capture(num_segments_storage) + num_segments_storage = project.optimize_capture( + num_segments_storage) # Update the loop variable and the progress bar. remaining_num_traces -= capture_cfg.num_segments @@ -403,7 +403,8 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: config: The capture configuration. file: The output file path. """ - if config["capture"]["show_plot"] and config["capture"]["scope_select"] != "none": + if config["capture"]["show_plot"] and config["capture"][ + "scope_select"] != "none": plot.save_plot_to_file( project.get_waves(0, config["capture"]["plot_traces"]), set_indices=None, @@ -413,8 +414,7 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: ) logger.info( f'Created plot with {config["capture"]["plot_traces"]} traces: ' - f'{Path(str(file) + ".html").resolve()}' - ) + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -453,8 +453,7 @@ def main(argv=None): # Configure cipher. device_id, owner_page, boot_log, boot_measurements, version = configure_cipher( - cfg, ot_hmac, ot_prng - ) + cfg, ot_hmac, ot_prng) # Capture traces. capture(scope, ot_hmac, capture_cfg, project, target) @@ -482,17 +481,16 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) if args.save_binary: - metadata["fw_bin"] = helpers.get_binary_blob(cfg["target"]["fw_bin"]) + metadata["fw_bin"] = helpers.get_binary_blob( + cfg["target"]["fw_bin"]) # Store user provided notes. metadata["notes"] = args.notes # Store the Git hash. diff --git a/capture/capture_ibex.py b/capture/capture_ibex.py index 5e5ea086..202ef245 100755 --- a/capture/capture_ibex.py +++ b/capture/capture_ibex.py @@ -2,6 +2,13 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 +"""Ibex SCA capture script. + +Captures power traces during different Ibex operations. + +Typical usage: +>>> ./capture_ibex.py -c configs/ibex_sca_cw310.yaml -p projects/ibex_sca_capture +""" import json import logging @@ -28,15 +35,6 @@ from target.targets import Target, TargetConfig from util import check_version, plot -"""Ibex SCA capture script. - -Captures power traces during different Ibex operations. - -Typical usage: ->>> ./capture_ibex.py -c configs/ibex_sca_cw310.yaml -p projects/ibex_sca_capture -""" - - logger = logging.getLogger() @@ -78,9 +76,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Init scope. scope_type = cfg["capture"]["scope_select"] @@ -101,23 +98,23 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["husky"].get("usb_serial"), + husky_serial=cfg["husky"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) if scope_type != "none": # Will determine sampling rate (for Husky only), if not given in cfg. - cfg[scope_type]["sampling_rate"] = determine_sampling_rate(cfg, scope_type) + cfg[scope_type]["sampling_rate"] = determine_sampling_rate( + cfg, scope_type) # Will convert number of cycles into number of samples if they are not given in cfg. cfg[scope_type]["num_samples"] = convert_num_cycles(cfg, scope_type) # Will convert offset in cycles into offset in samples, if they are not given in cfg. - cfg[scope_type]["offset_samples"] = convert_offset_cycles(cfg, scope_type) + cfg[scope_type]["offset_samples"] = convert_offset_cycles( + cfg, scope_type) - logger.info( - f"Initializing scope {scope_type} with a sampling rate of \ - {cfg[scope_type]['sampling_rate']}..." - ) # noqa: E501 + logger.info(f"Initializing scope {scope_type} with a sampling rate of \ + {cfg[scope_type]['sampling_rate']}...") # noqa: E501 # Determine if we are in batch mode or not. batch = False @@ -199,16 +196,17 @@ def generate_combi_response(fixed_data1, fixed_data2, trigger): sub = (0, (fixed_data1 - fixed_data2) & 0xFFFFFFFF)[trigger & 4] shift_operand = (fixed_data2 & 0xFFFFFFFF) % 32 shift = (fixed_data1 << shift_operand) & 0xFFFFFFFF | ( - fixed_data1 >> (32 - shift_operand) & 0xFFFFFFFF - ) + fixed_data1 >> (32 - shift_operand) & 0xFFFFFFFF) shift = (0, shift)[trigger & 8] mult = (0, (fixed_data1 * fixed_data2) & 0xFFFFFFFF)[trigger & 16] if fixed_data2 == 0: div = 0xFFFFFFFF - elif to_signed32(fixed_data1) == -2147483648 and to_signed32(fixed_data2) == -1: + elif to_signed32(fixed_data1) == -2147483648 and to_signed32( + fixed_data2) == -1: div = 0x80000000 else: - div = int(to_signed32(fixed_data1) / to_signed32(fixed_data2)) & 0xFFFFFFFF + div = int( + to_signed32(fixed_data1) / to_signed32(fixed_data2)) & 0xFFFFFFFF div = (0, div)[trigger & 32] data = [ @@ -300,11 +298,8 @@ def capture( # Check whether we are in batch mode. batch = False - if ( - "batch" in capture_cfg.test_mode or - "fvsr" in capture_cfg.test_mode or - "random" in capture_cfg.test_mode - ): + if ("batch" in capture_cfg.test_mode or "fvsr" in capture_cfg.test_mode or + "random" in capture_cfg.test_mode): batch = True # Seed the PRNG used for generating random data. @@ -318,9 +313,10 @@ def capture( signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project)) # Main capture with progress bar. remaining_num_traces = capture_cfg.num_traces - with tqdm( - total=remaining_num_traces, desc="Capturing", ncols=80, unit=" traces" - ) as pbar: + with tqdm(total=remaining_num_traces, + desc="Capturing", + ncols=80, + unit=" traces") as pbar: while remaining_num_traces > 0: # Arm the scope. if scope is not None: @@ -351,10 +347,12 @@ def capture( # In random batch, number of segments is transferred to the # device. number of segments random datasets are generated # on the device. Trigger is set number of segments. - ot_ibex.start_test(capture_cfg.test_mode, capture_cfg.num_segments) + ot_ibex.start_test(capture_cfg.test_mode, + capture_cfg.num_segments) else: # We use fixed data. - ot_ibex.start_test(capture_cfg.test_mode, capture_cfg.input_fixed) + ot_ibex.start_test(capture_cfg.test_mode, + capture_cfg.input_fixed) # Capture traces. if scope is not None: @@ -366,9 +364,9 @@ def capture( response = response_json["result"] # Generate data set used for the test. - data1, data2 = generate_ref_data( - capture_cfg.test_mode, capture_cfg.input_fixed, capture_cfg.num_segments - ) + data1, data2 = generate_ref_data(capture_cfg.test_mode, + capture_cfg.input_fixed, + capture_cfg.num_segments) # Store traces. if scope is not None: @@ -393,7 +391,8 @@ def capture( # Memory allocation optimization for CW trace library. if scope is not None: - num_segments_storage = project.optimize_capture(num_segments_storage) + num_segments_storage = project.optimize_capture( + num_segments_storage) # Check response. 0 for non-batch and the last data element in # batch mode. @@ -401,25 +400,20 @@ def capture( if batch: if "combi" in capture_cfg.test_mode: expected_response = generate_combi_response( - data1[-1], data2[-1], capture_cfg.trigger - ) + data1[-1], data2[-1], capture_cfg.trigger) assert response == expected_response, ( f"Incorrect encryption result!\n" f"actual: {response}\n" - f"expected: {expected_response}" - ) + f"expected: {expected_response}") else: assert response == data1[-1], ( f"Incorrect encryption result!\n" f"actual: {response}\n" - f"expected: {data1[-1]}" - ) + f"expected: {data1[-1]}") else: - assert response == 0, ( - f"Incorrect encryption result!\n" - f"actual: {response}\n" - f"expected: {0}" - ) + assert response == 0, (f"Incorrect encryption result!\n" + f"actual: {response}\n" + f"expected: {0}") # Update the loop variable and the progress bar. remaining_num_traces -= capture_cfg.num_segments @@ -436,7 +430,8 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: config: The capture configuration. file: The output file path. """ - if config["capture"]["show_plot"] and config["capture"]["scope_select"] != "none": + if config["capture"]["show_plot"] and config["capture"][ + "scope_select"] != "none": plot.save_plot_to_file( project.get_waves(0, config["capture"]["plot_traces"]), set_indices=None, @@ -446,8 +441,7 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: ) logger.info( f'Created plot with {config["capture"]["plot_traces"]} traces: ' - f'{Path(str(file) + ".html").resolve()}' - ) + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -466,11 +460,9 @@ def main(argv=None): # Setup the target, scope and project. target, scope, project = setup(cfg, args.project) - if not ( - "batch" in cfg["test"]["which_test"] or - "fvsr" in cfg["test"]["which_test"] or - "random" in cfg["test"]["which_test"] - ): + if not ("batch" in cfg["test"]["which_test"] or + "fvsr" in cfg["test"]["which_test"] or + "random" in cfg["test"]["which_test"]): cfg["capture"]["num_segments"] = 1 # Create capture config object. @@ -493,8 +485,7 @@ def main(argv=None): # Init the pentest framework and read the target info. device_id, owner_page, boot_log, boot_measurements, version = ot_ibex.init( - cfg["test"]["core_config"], cfg["test"]["sensor_config"] - ) + cfg["test"]["core_config"], cfg["test"]["sensor_config"]) # Capture traces. capture(scope, ot_ibex, ot_prng, capture_cfg, project, target) @@ -525,17 +516,16 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) if args.save_binary: - metadata["fw_bin"] = helpers.get_binary_blob(cfg["target"]["fw_bin"]) + metadata["fw_bin"] = helpers.get_binary_blob( + cfg["target"]["fw_bin"]) # Store user provided notes. metadata["notes"] = args.notes # Store the Git hash. diff --git a/capture/capture_kmac.py b/capture/capture_kmac.py index b78fbed6..b54cd2de 100755 --- a/capture/capture_kmac.py +++ b/capture/capture_kmac.py @@ -6,6 +6,16 @@ # Note: The word ciphertext refers to the tag in kmac # To be compatible to the other capture scripts, the variable is # called ciphertext +"""KMAC SCA capture script. + +Captures power traces during KMAC operations. + +The data format of the crypto material (ciphertext, plaintext, and key) inside +the script is stored in plain integer arrays. + +Typical usage: +>>> ./capture_kmac.py -c configs/kmac_sca_cw310.yaml -p projects/kmac_sca_capture +""" import binascii import json @@ -34,17 +44,6 @@ from target.targets import Target, TargetConfig from util import check_version, plot -"""KMAC SCA capture script. - -Captures power traces during KMAC operations. - -The data format of the crypto material (ciphertext, plaintext, and key) inside -the script is stored in plain integer arrays. - -Typical usage: ->>> ./capture_kmac.py -c configs/kmac_sca_cw310.yaml -p projects/kmac_sca_capture -""" - # Byte lengths of the text, key, and tag. text_length = 16 key_length = 16 @@ -90,9 +89,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Init scope. scope_type = cfg["capture"]["scope_select"] @@ -113,7 +111,7 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["husky"].get("usb_serial"), + husky_serial=cfg["husky"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) @@ -123,16 +121,16 @@ def setup(cfg: dict, project: Path): scope_type = cfg["capture"]["scope_select"] # Determine sampling rate, if necessary. - cfg[scope_type]["sampling_rate"] = determine_sampling_rate(cfg, scope_type) + cfg[scope_type]["sampling_rate"] = determine_sampling_rate( + cfg, scope_type) # Convert number of cycles into number of samples, if necessary. cfg[scope_type]["num_samples"] = convert_num_cycles(cfg, scope_type) # Convert offset in cycles into offset in samples, if necessary. - cfg[scope_type]["offset_samples"] = convert_offset_cycles(cfg, scope_type) + cfg[scope_type]["offset_samples"] = convert_offset_cycles( + cfg, scope_type) - logger.info( - f"Initializing scope {scope_type} with a sampling rate of \ - {cfg[scope_type]['sampling_rate']}..." - ) # noqa: E501 + logger.info(f"Initializing scope {scope_type} with a sampling rate of \ + {cfg[scope_type]['sampling_rate']}...") # noqa: E501 # Determine if we are in batch mode or not. batch = True @@ -221,8 +219,8 @@ def configure_cipher(cfg, ot_kmac, ot_prng): fpga_mode_bit = 1 # Initialize KMAC on the target. device_id, owner_page, boot_log, boot_measurements, version = ot_kmac.init( - fpga_mode_bit, cfg["test"]["core_config"], cfg["test"]["sensor_config"] - ) + fpga_mode_bit, cfg["test"]["core_config"], + cfg["test"]["sensor_config"]) # Configure PRNGs. # Seed the software LFSR used for initial key masking. @@ -300,8 +298,7 @@ def check_tag(target, expected_last_tag, capture_cfg): assert actual_last_tag == expected_last_tag, ( f"Incorrect encryption result!\n" f"actual: {actual_last_tag}\n" - f"expected: {expected_last_tag}" - ) + f"expected: {expected_last_tag}") def capture( @@ -340,7 +337,8 @@ def capture( # we should adjust this throughout all scripts. sample_fixed = 0 - logger.info(f"Initializing OT KMAC with key {binascii.b2a_hex(bytes(key))} ...") + logger.info( + f"Initializing OT KMAC with key {binascii.b2a_hex(bytes(key))} ...") if capture_cfg.capture_mode == "fvsr_key": ot_kmac.fvsr_key_set(key) else: @@ -353,9 +351,10 @@ def capture( signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project)) # Main capture with progress bar. remaining_num_traces = capture_cfg.num_traces - with tqdm( - total=remaining_num_traces, desc="Capturing", ncols=80, unit=" traces" - ) as pbar: + with tqdm(total=remaining_num_traces, + desc="Capturing", + ncols=80, + unit=" traces") as pbar: while remaining_num_traces > 0: # Arm the scope. if scope is not None: @@ -367,7 +366,8 @@ def capture( ot_kmac.absorb_batch(capture_cfg.num_segments) elif capture_cfg.capture_mode == "daisy_chain": text = tag[:text_length] - ot_kmac.absorb_daisy_chain(text, key_fixed, capture_cfg.num_segments) + ot_kmac.absorb_daisy_chain(text, key_fixed, + capture_cfg.num_segments) else: logger.info("Error: Mode not recognized.") return @@ -411,7 +411,8 @@ def capture( if scope is not None: # Memory allocation optimization for CW trace library. - num_segments_storage = project.optimize_capture(num_segments_storage) + num_segments_storage = project.optimize_capture( + num_segments_storage) # Update the loop variable and the progress bar. remaining_num_traces -= capture_cfg.num_segments @@ -428,7 +429,8 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: config: The capture configuration. file: The output file path. """ - if config["capture"]["show_plot"] and config["capture"]["scope_select"] != "none": + if config["capture"]["show_plot"] and config["capture"][ + "scope_select"] != "none": plot.save_plot_to_file( project.get_waves(0, config["capture"]["plot_traces"]), set_indices=None, @@ -438,8 +440,7 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: ) logger.info( f'Created plot with {config["capture"]["plot_traces"]} traces: ' - f'{Path(str(file) + ".html").resolve()}' - ) + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -477,8 +478,7 @@ def main(argv=None): # Configure cipher. device_id, owner_page, boot_log, boot_measurements, version = configure_cipher( - cfg, ot_kmac, ot_prng - ) + cfg, ot_kmac, ot_prng) # Configure trigger source. # 0 for HW, 1 for SW. @@ -513,17 +513,16 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) if args.save_binary: - metadata["fw_bin"] = helpers.get_binary_blob(cfg["target"]["fw_bin"]) + metadata["fw_bin"] = helpers.get_binary_blob( + cfg["target"]["fw_bin"]) # Store user provided notes. metadata["notes"] = args.notes # Store the Git hash. diff --git a/capture/capture_otbn.py b/capture/capture_otbn.py index a47a8eb7..180159cd 100755 --- a/capture/capture_otbn.py +++ b/capture/capture_otbn.py @@ -2,6 +2,17 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 +"""OTBN vertical SCA capture script. + +Captures power traces during OTBN operations. + +The data format of the crypto material (ciphertext, plaintext, and key) inside +the script is stored in plain integer arrays. + +Typical usage: +>>> ./capture_otbn.py -c configs/otbn_vertical_keygen_sca_cw310.yaml \ + -p projects/otbn_vertical_sca_cw310_keygen +""" import logging import random @@ -29,24 +40,11 @@ from util import data_generator as dg from util import plot -"""OTBN vertical SCA capture script. - -Captures power traces during OTBN operations. - -The data format of the crypto material (ciphertext, plaintext, and key) inside -the script is stored in plain integer arrays. - -Typical usage: ->>> ./capture_otbn.py -c configs/otbn_vertical_keygen_sca_cw310.yaml \ - -p projects/otbn_vertical_sca_cw310_keygen -""" - # Byte lengths of the text, key, and tag. plain_text_len_bytes = 40 text_len_bytes = 40 key_len_bytes = 40 - logger = logging.getLogger() @@ -104,9 +102,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Init scope. scope_type = cfg["capture"]["scope_select"] @@ -127,18 +124,20 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["husky"].get("usb_serial"), + husky_serial=cfg["husky"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) if scope_type != "none": # Determine sampling rate, if necessary. - cfg[scope_type]["sampling_rate"] = determine_sampling_rate(cfg, scope_type) + cfg[scope_type]["sampling_rate"] = determine_sampling_rate( + cfg, scope_type) # Convert number of cycles into number of samples, if necessary. cfg[scope_type]["num_samples"] = convert_num_cycles(cfg, scope_type) # Convert offset in cycles into offset in samples, if necessary. - cfg[scope_type]["offset_samples"] = convert_offset_cycles(cfg, scope_type) + cfg[scope_type]["offset_samples"] = convert_offset_cycles( + cfg, scope_type) logger.info( f"Initializing scope {scope_type} with a sampling rate of {cfg[scope_type]['sampling_rate']}..." # noqa: E501 @@ -220,9 +219,8 @@ def establish_communication(target): return ot_otbn_vert, ot_trig, ot_prng -def configure_cipher( - cfg: dict, capture_cfg: CaptureConfig, ot_otbn_vert, ot_prng -) -> OTOTBN: +def configure_cipher(cfg: dict, capture_cfg: CaptureConfig, ot_otbn_vert, + ot_prng) -> OTOTBN: """Configure the OTBN app. Establish communication with the OTBN keygen app and configure the seed. @@ -247,7 +245,8 @@ def configure_cipher( if cfg["test"]["curve"] == "p256": # Create curve config object curve_cfg = CurveConfig( - curve_order_n=0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551, + curve_order_n= + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551, key_bytes=256 // 8, seed_bytes=320 // 8, modinv_share_bytes=320 // 8, @@ -263,7 +262,8 @@ def configure_cipher( modinv_mask_bytes=0, ) # TODO: add support for P384 - raise NotImplementedError(f'Curve {cfg["test"]["curve"]} is not supported') + raise NotImplementedError( + f'Curve {cfg["test"]["curve"]} is not supported') if capture_cfg.capture_mode == "keygen": if capture_cfg.batch_mode: @@ -287,8 +287,7 @@ def configure_cipher( seed_fixed_int = int.from_bytes(capture_cfg.C, byteorder='little') + \ int.from_bytes(fixed_number, byteorder='little') capture_cfg.seed_fixed = seed_fixed_int.to_bytes( - curve_cfg.seed_bytes, byteorder="little" - ) + curve_cfg.seed_bytes, byteorder="little") else: # In fixed-vs-random SEED mode we use only one fixed constant: # 1. seed_fixed - A 320 bit constant used to derive the fixed key @@ -302,9 +301,8 @@ def configure_cipher( # Expected key is `seed mod n`, where n is the order of the curve and # `seed` is interpreted as little-endian. capture_cfg.expected_fixed_key = ( - int.from_bytes(capture_cfg.seed_fixed, byteorder="little") - % curve_cfg.curve_order_n - ) + int.from_bytes(capture_cfg.seed_fixed, byteorder="little") % + curve_cfg.curve_order_n) elif capture_cfg.capture_mode == "modinv": if capture_cfg.batch_mode: # TODO: add support for batch mode @@ -316,23 +314,20 @@ def configure_cipher( # r1, r2, r3 = dg.get_random() # capture_cfg.k_fixed = (bytearray(r1) + bytearray(r2) + # bytearray(r3))[:curve_cfg.key_bytes] - capture_cfg.k_fixed = bytearray( - ( - 0x2648D0D248B70944DFD84C2F85EA5793729112E7CAFA50ABDF7EF8B7594FA2A1 - ).to_bytes(curve_cfg.key_bytes, "little") - ) - k_fixed_int = int.from_bytes(capture_cfg.k_fixed, byteorder="little") + capture_cfg.k_fixed = bytearray(( + 0x2648D0D248B70944DFD84C2F85EA5793729112E7CAFA50ABDF7EF8B7594FA2A1 + ).to_bytes(curve_cfg.key_bytes, "little")) + k_fixed_int = int.from_bytes(capture_cfg.k_fixed, + byteorder="little") # Expected fixed output is `(k)^(-1) mod n`, where n is the curve order n - capture_cfg.expected_fixed_output = pow( - k_fixed_int, -1, curve_cfg.curve_order_n - ) + capture_cfg.expected_fixed_output = pow(k_fixed_int, -1, + curve_cfg.curve_order_n) return curve_cfg -def generate_ref_crypto_keygen( - cfg: dict, sample_fixed, curve_cfg: CurveConfig, capture_cfg: CaptureConfig -): +def generate_ref_crypto_keygen(cfg: dict, sample_fixed, curve_cfg: CurveConfig, + capture_cfg: CaptureConfig): """Generate cipher material for keygen application. Args: @@ -405,9 +400,8 @@ def generate_ref_crypto_keygen( return seed_used, mask, expected_key, sample_fixed -def generate_ref_crypto_modinv( - cfg: dict, sample_fixed, curve_cfg: CurveConfig, capture_cfg: CaptureConfig -): +def generate_ref_crypto_modinv(cfg: dict, sample_fixed, curve_cfg: CurveConfig, + capture_cfg: CaptureConfig): """Generate cipher material for the modular inverse operation. Args: @@ -442,14 +436,13 @@ def generate_ref_crypto_modinv( # adapt share k1 so that k = (k0 + k1) mod n k_tmp = (k0_fixed + k1_fixed) % curve_cfg.curve_order_n k_tmp_diff = ( - int.from_bytes(capture_cfg.k_fixed, byteorder="little") - k_tmp - ) % curve_cfg.curve_order_n + int.from_bytes(capture_cfg.k_fixed, byteorder="little") - + k_tmp) % curve_cfg.curve_order_n k1_fixed += k_tmp_diff if k1_fixed >= pow(2, 320): k1_fixed -= curve_cfg.curve_order_n input_k1_fixed = bytearray( - (k1_fixed).to_bytes(curve_cfg.modinv_share_bytes, "little") - ) + (k1_fixed).to_bytes(curve_cfg.modinv_share_bytes, "little")) # Use the fixed input. input_k0_used = input_k0_fixed input_k1_used = input_k1_fixed @@ -464,11 +457,11 @@ def generate_ref_crypto_modinv( input_k1_used = (bytearray(r1) + bytearray(r2) + bytearray(r3))[:curve_cfg.modinv_share_bytes] # calculate the key from the shares - k_used_int = ( - int.from_bytes(input_k0_used, byteorder="little") + - int.from_bytes(input_k1_used, byteorder="little") - ) % curve_cfg.curve_order_n - k_used = bytearray(k_used_int.to_bytes(curve_cfg.key_bytes, "little")) + k_used_int = (int.from_bytes(input_k0_used, byteorder="little") + + int.from_bytes(input_k1_used, byteorder="little") + ) % curve_cfg.curve_order_n + k_used = bytearray( + k_used_int.to_bytes(curve_cfg.key_bytes, "little")) expected_output = pow(k_used_int, -1, curve_cfg.curve_order_n) # The next sample is either fixed or random. @@ -478,7 +471,8 @@ def generate_ref_crypto_modinv( return k_used, input_k0_used, input_k1_used, expected_output, sample_fixed -def check_ciphertext_keygen(ot_otbn_vert: OTOTBN, expected_key, curve_cfg: CurveConfig): +def check_ciphertext_keygen(ot_otbn_vert: OTOTBN, expected_key, + curve_cfg: CurveConfig): """Compares the received with the generated key. Key shares are read from the device and compared against the pre-computed @@ -507,18 +501,15 @@ def check_ciphertext_keygen(ot_otbn_vert: OTOTBN, expected_key, curve_cfg: Curve d1 = int.from_bytes(share1, byteorder="little") actual_key = (d0 + d1) % curve_cfg.curve_order_n - assert actual_key == expected_key, ( - f"Incorrect encryption result!\n" - f"actual: {actual_key}\n" - f"expected: {expected_key}" - ) + assert actual_key == expected_key, (f"Incorrect encryption result!\n" + f"actual: {actual_key}\n" + f"expected: {expected_key}") return share0, share1 -def check_ciphertext_modinv( - ot_otbn_vert: OTOTBN, expected_output, curve_cfg: CurveConfig -): +def check_ciphertext_modinv(ot_otbn_vert: OTOTBN, expected_output, + curve_cfg: CurveConfig): """Compares the received modular inverse output with the generated output. Args: @@ -538,17 +529,13 @@ def check_ciphertext_modinv( raise RuntimeError("alpha is none") # Actual result (kalpha_inv*alpha) mod n: - actual_output = ( - int.from_bytes(kalpha_inv, byteorder="little") * - int.from_bytes(alpha, byteorder="little") % - curve_cfg.curve_order_n - ) + actual_output = (int.from_bytes(kalpha_inv, byteorder="little") * + int.from_bytes(alpha, byteorder="little") % + curve_cfg.curve_order_n) - assert actual_output == expected_output, ( - f"Incorrect modinv result!\n" - f"actual: {actual_output}\n" - f"expected: {expected_output}" - ) + assert actual_output == expected_output, (f"Incorrect modinv result!\n" + f"actual: {actual_output}\n" + f"expected: {expected_output}") return actual_output @@ -590,17 +577,17 @@ def capture_keygen( signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project)) # Main capture with progress bar. remaining_num_traces = capture_cfg.num_traces - with tqdm( - total=remaining_num_traces, desc="Capturing", ncols=80, unit=" traces" - ) as pbar: + with tqdm(total=remaining_num_traces, + desc="Capturing", + ncols=80, + unit=" traces") as pbar: while remaining_num_traces > 0: # Arm the scope. if scope is not None: scope.arm() seed_used, mask, expected_key, sample_fixed = generate_ref_crypto_keygen( - cfg, sample_fixed, curve_cfg, capture_cfg - ) + cfg, sample_fixed, curve_cfg, capture_cfg) # Trigger encryption. if capture_cfg.batch_mode: @@ -622,8 +609,7 @@ def capture_keygen( # Compare received key with generated key. share0, share1 = check_ciphertext_keygen( - ot_otbn_vert, expected_key, curve_cfg - ) + ot_otbn_vert, expected_key, curve_cfg) # Store trace into database. if scope is not None: @@ -636,7 +622,8 @@ def capture_keygen( # Memory allocation optimization for CW trace library. if scope is not None: - num_segments_storage = project.optimize_capture(num_segments_storage) + num_segments_storage = project.optimize_capture( + num_segments_storage) # Update the loop variable and the progress bar. remaining_num_traces -= capture_cfg.num_segments @@ -678,17 +665,18 @@ def capture_modinv( signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project)) # Main capture with progress bar. remaining_num_traces = capture_cfg.num_traces - with tqdm( - total=remaining_num_traces, desc="Capturing", ncols=80, unit=" traces" - ) as pbar: + with tqdm(total=remaining_num_traces, + desc="Capturing", + ncols=80, + unit=" traces") as pbar: while remaining_num_traces > 0: # Arm the scope. if scope is not None: scope.arm() k_used, input_k0_used, input_k1_used, expected_output, sample_fixed = ( - generate_ref_crypto_modinv(cfg, sample_fixed, curve_cfg, capture_cfg) - ) + generate_ref_crypto_modinv(cfg, sample_fixed, curve_cfg, + capture_cfg)) # Trigger encryption. if capture_cfg.batch_mode: @@ -705,8 +693,7 @@ def capture_modinv( # Compare received key with generated key. actual_output = check_ciphertext_modinv( - ot_otbn_vert, expected_output, curve_cfg - ) + ot_otbn_vert, expected_output, curve_cfg) # Store trace into database. if scope is not None: @@ -714,14 +701,15 @@ def capture_modinv( wave=waves[0, :], plaintext=k_used, ciphertext=bytearray( - actual_output.to_bytes(curve_cfg.key_bytes, "little") - ), + actual_output.to_bytes(curve_cfg.key_bytes, + "little")), key=k_used, ) # Memory allocation optimization for CW trace library. if scope is not None: - num_segments_storage = project.optimize_capture(num_segments_storage) + num_segments_storage = project.optimize_capture( + num_segments_storage) # Update the loop variable and the progress bar. remaining_num_traces -= capture_cfg.num_segments @@ -738,7 +726,8 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: config: The capture configuration. file: The output file path. """ - if config["capture"]["show_plot"] and config["capture"]["scope_select"] != "none": + if config["capture"]["show_plot"] and config["capture"][ + "scope_select"] != "none": plot.save_plot_to_file( project.get_waves(0, config["capture"]["plot_traces"]), set_indices=None, @@ -748,8 +737,7 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: ) logger.info( f'Created plot with {config["capture"]["plot_traces"]} traces: ' - f'{Path(str(file) + ".html").resolve()}' - ) + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -805,13 +793,11 @@ def main(argv=None): # Capture traces. if mode == "keygen": - capture_keygen( - cfg, scope, ot_otbn_vert, capture_cfg, curve_cfg, project, target - ) + capture_keygen(cfg, scope, ot_otbn_vert, capture_cfg, curve_cfg, + project, target) elif mode == "modinv": - capture_modinv( - cfg, scope, ot_otbn_vert, capture_cfg, curve_cfg, project, target - ) + capture_modinv(cfg, scope, ot_otbn_vert, capture_cfg, curve_cfg, + project, target) else: # TODO: add support for modinv app raise NotImplementedError("Cofigured OTBN app not yet supported.") @@ -828,9 +814,8 @@ def main(argv=None): metadata["offset_samples"] = scope.scope_cfg.offset_samples metadata["scope_gain"] = scope.scope_cfg.scope_gain if cfg["capture"]["scope_select"] == "husky": - metadata["sampling_rate"] = ( - scope.scope.scope.clock.adc_freq / scope.scope.scope.adc.decimate - ) + metadata["sampling_rate"] = (scope.scope.scope.clock.adc_freq / + scope.scope.scope.adc.decimate) metadata["samples_trigger_high"] = scope.scope.scope.adc.trig_count else: metadata["sampling_rate"] = scope.scope_cfg.sampling_rate @@ -840,17 +825,16 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) if args.save_binary: - metadata["fw_bin"] = helpers.get_binary_blob(cfg["target"]["fw_bin"]) + metadata["fw_bin"] = helpers.get_binary_blob( + cfg["target"]["fw_bin"]) # Store user provided notes. metadata["notes"] = args.notes # Store the Git hash. diff --git a/capture/capture_sha3.py b/capture/capture_sha3.py index 2ecab008..083c9957 100755 --- a/capture/capture_sha3.py +++ b/capture/capture_sha3.py @@ -76,9 +76,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Init scope. scope_type = cfg["capture"]["scope_select"] @@ -99,23 +98,23 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["husky"].get("usb_serial"), + husky_serial=cfg["husky"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) if scope_type != "none": # Determine sampling rate, if necessary. - cfg[scope_type]["sampling_rate"] = determine_sampling_rate(cfg, scope_type) + cfg[scope_type]["sampling_rate"] = determine_sampling_rate( + cfg, scope_type) # Convert number of cycles into number of samples, if necessary. cfg[scope_type]["num_samples"] = convert_num_cycles(cfg, scope_type) # Convert offset in cycles into offset in samples, if necessary. - cfg[scope_type]["offset_samples"] = convert_offset_cycles(cfg, scope_type) + cfg[scope_type]["offset_samples"] = convert_offset_cycles( + cfg, scope_type) - logger.info( - f"Initializing scope {scope_type} with a sampling rate of \ - {cfg[scope_type]['sampling_rate']}..." - ) # noqa: E501 + logger.info(f"Initializing scope {scope_type} with a sampling rate of \ + {cfg[scope_type]['sampling_rate']}...") # noqa: E501 # Determine if we are in batch mode or not. batch = True @@ -204,8 +203,8 @@ def configure_cipher(cfg, ot_sha3, ot_prng): fpga_mode_bit = 1 # Initialize KMAC on the target. device_id, owner_page, boot_log, boot_measurements, version = ot_sha3.init( - fpga_mode_bit, cfg["test"]["core_config"], cfg["test"]["sensor_config"] - ) + fpga_mode_bit, cfg["test"]["core_config"], + cfg["test"]["sensor_config"]) if cfg["test"]["masks_off"] is True: logger.info("Configure device to use constant, fast entropy!") @@ -277,11 +276,11 @@ def check_digest(received_output, expected_output): assert received_output == expected_output, ( f"Incorrect encryption result!\n" f"actual: {received_output}\n" - f"expected: {expected_output}" - ) + f"expected: {expected_output}") -def init_target(cfg: dict, capture_cfg: CaptureConfig, target: Target, text_fixed): +def init_target(cfg: dict, capture_cfg: CaptureConfig, target: Target, + text_fixed): """Initializes the target. Establish a communication interface with the target and configure the cipher. @@ -300,8 +299,7 @@ def init_target(cfg: dict, capture_cfg: CaptureConfig, target: Target, text_fixe # Configure cipher. device_id, owner_page, boot_log, boot_measurements, version = configure_cipher( - cfg, ot_sha3, ot_prng - ) + cfg, ot_sha3, ot_prng) # Configure trigger source. # 0 for HW, 1 for SW. @@ -353,16 +351,16 @@ def capture( # Initialize target. ot_sha3, device_id, owner_page, boot_log, boot_measurements, version = init_target( - cfg, capture_cfg, target, text_fixed - ) + cfg, capture_cfg, target, text_fixed) # Register ctrl-c handler to store traces on abort. signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project)) # Main capture with progress bar. remaining_num_traces = capture_cfg.num_traces - with tqdm( - total=remaining_num_traces, desc="Capturing", ncols=80, unit=" traces" - ) as pbar: + with tqdm(total=remaining_num_traces, + desc="Capturing", + ncols=80, + unit=" traces") as pbar: while remaining_num_traces > 0: # Arm the scope. if scope is not None: @@ -410,7 +408,8 @@ def capture( # Memory allocation optimization for CW trace library. if scope is not None: - num_segments_storage = project.optimize_capture(num_segments_storage) + num_segments_storage = project.optimize_capture( + num_segments_storage) # Update the loop variable and the progress bar. remaining_num_traces -= capture_cfg.num_segments @@ -429,7 +428,8 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: config: The capture configuration. file: The output file path. """ - if config["capture"]["show_plot"] and config["capture"]["scope_select"] != "none": + if config["capture"]["show_plot"] and config["capture"][ + "scope_select"] != "none": plot.save_plot_to_file( project.get_waves(0, config["capture"]["plot_traces"]), set_indices=None, @@ -437,10 +437,8 @@ def print_plot(project: SCAProject, config: dict, file: Path) -> None: outfile=file, add_mean_stddev=True, ) - print( - f'Created plot with {config["capture"]["plot_traces"]} traces: ' - f'{Path(str(file) + ".html").resolve()}' - ) + print(f'Created plot with {config["capture"]["plot_traces"]} traces: ' + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -474,8 +472,7 @@ def main(argv=None): # Capture traces. device_id, owner_page, boot_log, boot_measurements, version = capture( - scope, cfg, capture_cfg, project, target - ) + scope, cfg, capture_cfg, project, target) # Print plot. print_plot(project, cfg, args.project) @@ -500,17 +497,16 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) if args.save_binary: - metadata["fw_bin"] = helpers.get_binary_blob(cfg["target"]["fw_bin"]) + metadata["fw_bin"] = helpers.get_binary_blob( + cfg["target"]["fw_bin"]) # Store user provided notes. metadata["notes"] = args.notes # Store the Git hash. diff --git a/capture/project_library/ot_trace_library/trace_library.py b/capture/project_library/ot_trace_library/trace_library.py index 60a05a5b..bab1c373 100644 --- a/capture/project_library/ot_trace_library/trace_library.py +++ b/capture/project_library/ot_trace_library/trace_library.py @@ -48,9 +48,11 @@ class TraceLibrary: database after reaching a trace memory threshold. """ - def __init__( - self, db_name, trace_threshold, wave_datatype=np.uint16, overwrite=False - ): + def __init__(self, + db_name, + trace_threshold, + wave_datatype=np.uint16, + overwrite=False): # If .db extension is not provided, add it to the file. if not db_name.endswith(".db"): db_name = db_name + ".db" @@ -66,7 +68,10 @@ def __init__( self.traces_table = db.Table( "traces", self.metadata, - db.Column("trace_id", db.Integer, primary_key=True, autoincrement=True), + db.Column("trace_id", + db.Integer, + primary_key=True, + autoincrement=True), db.Column("wave", db.LargeBinary), db.Column("plaintext", db.LargeBinary), db.Column("ciphertext", db.LargeBinary), @@ -74,9 +79,8 @@ def __init__( db.Column("x_pos", db.Integer), db.Column("y_pos", db.Integer), ) - self.metadata_table = db.Table( - "metadata", self.metadata, db.Column("data", db.PickleType) - ) + self.metadata_table = db.Table("metadata", self.metadata, + db.Column("data", db.PickleType)) self.metadata.create_all(self.engine, checkfirst=True) self.trace_mem = [] self.trace_mem_thr = trace_threshold @@ -118,7 +122,9 @@ def write_to_buffer(self, trace): if len(self.trace_mem) >= self.trace_mem_thr: self.flush_to_disk() - def get_traces(self, start: Optional[int] = None, end: Optional[int] = None): + def get_traces(self, + start: Optional[int] = None, + end: Optional[int] = None): """Get traces from database and stored into RAM. Fetch traces from start to end from database storage into RAM. @@ -136,24 +142,23 @@ def get_traces(self, start: Optional[int] = None, end: Optional[int] = None): start = start + 1 query = db.select(self.traces_table).where( (self.traces_table.c.trace_id >= start) & - (self.traces_table.c.trace_id <= end) - ) + (self.traces_table.c.trace_id <= end)) elif start is not None: # SQL ID starts at 1. start = start + 1 - query = db.select(self.traces_table).where( - self.traces_table.c.trace_id == start - ) + query = db.select( + self.traces_table).where(self.traces_table.c.trace_id == start) else: query = db.select(self.traces_table) return [ - Trace(**trace._mapping) for trace in self.session.execute(query).fetchall() + Trace(**trace._mapping) + for trace in self.session.execute(query).fetchall() ] - def get_waves_bytearray( - self, start: Optional[int] = None, end: Optional[int] = None - ): + def get_waves_bytearray(self, + start: Optional[int] = None, + end: Optional[int] = None): """Get all waves from the database. Returns: @@ -161,7 +166,9 @@ def get_waves_bytearray( """ return [trace.wave for trace in self.get_traces(start, end)] - def get_waves(self, start: Optional[int] = None, end: Optional[int] = None): + def get_waves(self, + start: Optional[int] = None, + end: Optional[int] = None): """Get all waves from the database in the trace array format. Returns: @@ -176,40 +183,34 @@ def get_waves(self, start: Optional[int] = None, end: Optional[int] = None): else: return waves - def get_plaintexts(self, start: Optional[int] = None, end: Optional[int] = None): + def get_plaintexts(self, + start: Optional[int] = None, + end: Optional[int] = None): """Get all plaintexts between start and end from the database in the int8 array format. Returns: The int plaintexts from the database. """ - plaintexts = [ - ( - None - if trace.plaintext is None - else np.frombuffer(trace.plaintext, np.uint8) - ) - for trace in self.get_traces(start, end) - ] + plaintexts = [(None if trace.plaintext is None else np.frombuffer( + trace.plaintext, np.uint8)) + for trace in self.get_traces(start, end)] # noqa: E126 if len(plaintexts) == 1: return plaintexts[0] else: return plaintexts - def get_ciphertexts(self, start: Optional[int] = None, end: Optional[int] = None): + def get_ciphertexts(self, + start: Optional[int] = None, + end: Optional[int] = None): """Get all ciphertexts between start and end from the database in the int8 array format. Returns: The int ciphertexts from the database. """ - ciphertexts = [ - ( - None - if trace.ciphertext is None - else np.frombuffer(trace.ciphertext, np.uint8) - ) - for trace in self.get_traces(start, end) - ] + ciphertexts = [(None if trace.ciphertext is None else np.frombuffer( + trace.ciphertext, np.uint8)) + for trace in self.get_traces(start, end)] # noqa: E126 if len(ciphertexts) == 1: return ciphertexts[0] else: @@ -254,5 +255,6 @@ def get_metadata(self): The metadata from the database. """ query = db.select(self.metadata_table) - metadata = Metadata(**self.session.execute(query).fetchall()[0]._mapping) + metadata = Metadata( + **self.session.execute(query).fetchall()[0]._mapping) return pickle.loads(bytes(metadata.data, encoding="latin1")) diff --git a/capture/project_library/project.py b/capture/project_library/project.py index deb243dd..53a04db9 100644 --- a/capture/project_library/project.py +++ b/capture/project_library/project.py @@ -46,8 +46,7 @@ def create_project(self): """ if self.project_cfg.type == "cw": self.project = cw.create_project( - self.project_cfg.path, overwrite=self.project_cfg.overwrite - ) + self.project_cfg.path, overwrite=self.project_cfg.overwrite) elif self.project_cfg.type == "ot_trace_library": self.project = TraceLibrary( str(self.project_cfg.path), @@ -57,8 +56,7 @@ def create_project(self): ) else: raise RuntimeError( - "Only trace_db='cw' or trace_db='ot_trace_library' supported." - ) + "Only trace_db='cw' or trace_db='ot_trace_library' supported.") def open_project(self) -> None: """Open project.""" @@ -92,14 +90,18 @@ def append_trace(self, wave, plaintext, ciphertext, key) -> None: """Append trace to trace storage in project.""" if self.project_cfg.type == "cw": trace = cw.Trace(wave, plaintext, ciphertext, key) - self.project.traces.append(trace, dtype=self.project_cfg.wave_dtype) + self.project.traces.append(trace, + dtype=self.project_cfg.wave_dtype) elif self.project_cfg.type == "ot_trace_library": - trace = Trace( - wave=wave.tobytes(), plaintext=plaintext, ciphertext=ciphertext, key=key - ) + trace = Trace(wave=wave.tobytes(), + plaintext=plaintext, + ciphertext=ciphertext, + key=key) self.project.write_to_buffer(trace) - def get_waves(self, start: Optional[int] = None, end: Optional[int] = None): + def get_waves(self, + start: Optional[int] = None, + end: Optional[int] = None): """Get waves from project.""" if self.project_cfg.type == "cw": if (start is not None) and (end is not None): @@ -123,7 +125,9 @@ def get_keys(self, start: Optional[int] = None, end: Optional[int] = None): elif self.project_cfg.type == "ot_trace_library": return self.project.get_keys(start, end) - def get_plaintexts(self, start: Optional[int] = None, end: Optional[int] = None): + def get_plaintexts(self, + start: Optional[int] = None, + end: Optional[int] = None): """Get plaintexts[start, end] from project.""" if self.project_cfg.type == "cw": if (start is not None) and (end is not None): @@ -135,7 +139,9 @@ def get_plaintexts(self, start: Optional[int] = None, end: Optional[int] = None) elif self.project_cfg.type == "ot_trace_library": return self.project.get_plaintexts(start, end) - def get_ciphertexts(self, start: Optional[int] = None, end: Optional[int] = None): + def get_ciphertexts(self, + start: Optional[int] = None, + end: Optional[int] = None): """Get ciphertexts[start, end] from project.""" if self.project_cfg.type == "cw": if (start is not None) and (end is not None): @@ -170,7 +176,8 @@ def optimize_capture(self, num_segments_storage): # See addWave() in chipwhisperer/common/traces/_base.py. if self.project_cfg.type == "cw": if self.project.traces.cur_seg.tracehint < self.project.traces.seg_len: - self.project.traces.cur_seg.setTraceHint(self.project.traces.seg_len) + self.project.traces.cur_seg.setTraceHint( + self.project.traces.seg_len) # Only keep the latest two trace storage segments enabled. By default the ChipWhisperer # API keeps all segments enabled and after appending a new trace, the trace ranges are # updated for all segments. This leads to a decreasing capture rate after time. @@ -183,8 +190,7 @@ def optimize_capture(self, num_segments_storage): if num_segments_storage != len(self.project.segments): if num_segments_storage >= 2: self.project.traces.tm.setTraceSegmentStatus( - num_segments_storage - 2, False - ) + num_segments_storage - 2, False) num_segments_storage = len(self.project.segments) return num_segments_storage diff --git a/capture/scopes/chipwhisperer/cw_segmented.py b/capture/scopes/chipwhisperer/cw_segmented.py index dfedd101..3e5b59a8 100644 --- a/capture/scopes/chipwhisperer/cw_segmented.py +++ b/capture/scopes/chipwhisperer/cw_segmented.py @@ -1,7 +1,6 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - """Support for capturing traces using ChipWhisperer-Husky in segmented mode.""" import time @@ -121,10 +120,13 @@ def num_samples(self, num_samples): self._num_samples_actual = num_samples if self.num_segments > self.num_segments_max: - print(f"Warning: Adjusting number of segments to {self.num_segments_max}.") + print( + f"Warning: Adjusting number of segments to {self.num_segments_max}." + ) self.num_segments = self.num_segments_max - def _configure_scope(self, scope_gain, offset_samples, clkgen_freq, adc_mul): + def _configure_scope(self, scope_gain, offset_samples, clkgen_freq, + adc_mul): self._scope.gain.db = scope_gain if offset_samples >= 0: self._scope.adc.offset = offset_samples @@ -145,14 +147,10 @@ def _configure_scope(self, scope_gain, offset_samples, clkgen_freq, adc_mul): assert self._scope.clock.adc_locked, "ADC failed to lock" def _print_device_info(self): - print( - ( - "Connected to ChipWhisperer (" - f"num_samples: {self.num_samples}, " - f"num_samples_actual: {self._num_samples_actual}, " - f"num_segments_actual: {self.num_segments_actual})" - ) - ) + print(("Connected to ChipWhisperer (" + f"num_samples: {self.num_samples}, " + f"num_samples_actual: {self._num_samples_actual}, " + f"num_segments_actual: {self.num_segments_actual})")) def arm(self): """Arms ChipWhisperer.""" diff --git a/capture/scopes/chipwhisperer/husky.py b/capture/scopes/chipwhisperer/husky.py index 75bd8a7b..47b69ceb 100644 --- a/capture/scopes/chipwhisperer/husky.py +++ b/capture/scopes/chipwhisperer/husky.py @@ -14,6 +14,7 @@ class Husky: + def __init__( self, scope_gain, @@ -37,8 +38,7 @@ def __init__( self.sampling_rate = sampling_rate if self.sampling_rate % self.clkgen_freq: raise RuntimeError( - "sampling_rate % (target_frequency / target_clk_mult) != 0" - ) + "sampling_rate % (target_frequency / target_clk_mult) != 0") self.adc_mul = int(self.sampling_rate / self.clkgen_freq) self.offset_samples = offset_samples @@ -79,7 +79,8 @@ def initialize_scope(self): ping_cnt = 0 while not scope.clock.adc_locked: if ping_cnt == 3: - raise RuntimeError(f"ADC failed to lock (attempts: {ping_cnt}).") + raise RuntimeError( + f"ADC failed to lock (attempts: {ping_cnt}).") ping_cnt += 1 time.sleep(0.5) self.scope = scope @@ -96,27 +97,21 @@ def configure_batch_mode(self): ) # Determine max. possible number of segments. - num_segments_max = ( - self.scope._scope.adc.oa.hwMaxSegmentSamples // - self.scope._scope.adc.samples - ) + num_segments_max = (self.scope._scope.adc.oa.hwMaxSegmentSamples // + self.scope._scope.adc.samples) # If num_segments is not provided in the config file, set it to # max. number of segments. if self.num_segments is None: self.num_segments = num_segments_max - print( - f"Info: num_segments not provided, setting to " - f"num_segments_max={num_segments_max}." - ) + print(f"Info: num_segments not provided, setting to " + f"num_segments_max={num_segments_max}.") self.scope.num_segments = self.num_segments # Sanity check manually set num_segments. Check, if we can keep the # num_segements * num_samples in memory. if self.num_segments > num_segments_max: - raise RuntimeError( - "num_segments too large, cannot keep\ - samples in CW Husky sample memory." - ) + raise RuntimeError("num_segments too large, cannot keep\ + samples in CW Husky sample memory.") def arm(self): self.scope.arm() diff --git a/capture/scopes/scope.py b/capture/scopes/scope.py index 597c3b5c..5fb56b32 100644 --- a/capture/scopes/scope.py +++ b/capture/scopes/scope.py @@ -79,19 +79,17 @@ def _init_scope(self): setup_data = scope._ask("PANEL_SETUP?") scope._get_and_print_cmd_error() tmp_sampling_rate = int( - re.findall(r"SampleRate = \d+", setup_data)[0][13:] - ) + re.findall(r"SampleRate = \d+", setup_data)[0][13:]) # Sanitize inputs. if self.scope_cfg.sampling_rate is not None: if self.scope_cfg.sampling_rate != tmp_sampling_rate: raise RuntimeError( "WAVERUNNER: Error: WaveRunner sampling " - "rate does not match given configuration!" - ) + "rate does not match given configuration!") if self.scope_cfg.num_segments is None: raise RuntimeError( - "WAVERUNNER: Error: num_segments needs to " "be provided!" - ) + "WAVERUNNER: Error: num_segments needs to " + "be provided!") # Configure WaveRunner. scope.configure_waveform_transfer_general( num_segments=self.scope_cfg.num_segments, @@ -113,10 +111,8 @@ def _sanitize_num_segments(self) -> None: """ if not self.scope_cfg.batch_mode and self.scope_cfg.num_segments != 1: self.scope_cfg.num_segments = 1 - print( - "Warning: num_segments needs to be 1 in non-batch mode. " - "Setting num_segments=1." - ) + print("Warning: num_segments needs to be 1 in non-batch mode. " + "Setting num_segments=1.") def arm(self) -> None: """Arm the scope.""" @@ -146,9 +142,8 @@ def convert_num_cycles(cfg: dict, scope_type: str) -> int: The number of samples. """ if cfg[scope_type].get("num_samples") is None: - sampl_target_rat = cfg[scope_type].get("sampling_rate") / cfg["target"].get( - "target_freq" - ) + sampl_target_rat = cfg[scope_type].get( + "sampling_rate") / cfg["target"].get("target_freq") num_samples = int(cfg[scope_type].get("num_cycles") * sampl_target_rat) if scope_type == "husky": @@ -171,9 +166,8 @@ def convert_offset_cycles(cfg: dict, scope_type: str) -> int: The offset in samples. """ if cfg[scope_type].get("offset_samples") is None: - sampl_target_rat = cfg[scope_type].get("sampling_rate") / cfg["target"].get( - "target_freq" - ) + sampl_target_rat = cfg[scope_type].get( + "sampling_rate") / cfg["target"].get("target_freq") return int(cfg[scope_type].get("offset_cycles") * sampl_target_rat) else: return cfg[scope_type].get("offset_samples") @@ -204,7 +198,6 @@ def determine_sampling_rate(cfg: dict, scope_type: str) -> int: # Waverunner init not done yet, so cannot be read from WaveRunner. raise RuntimeError( "WAVERUNNER: ERROR: Sampling rate for WaveRunner " - "not given in configuration." - ) + "not given in configuration.") else: return cfg[scope_type].get("sampling_rate") diff --git a/capture/scopes/waverunner/test_waverunner.py b/capture/scopes/waverunner/test_waverunner.py index f6eb6353..12f314a4 100755 --- a/capture/scopes/waverunner/test_waverunner.py +++ b/capture/scopes/waverunner/test_waverunner.py @@ -2,7 +2,6 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - """Test WaveRunner class.""" from datetime import datetime diff --git a/capture/scopes/waverunner/waverunner.py b/capture/scopes/waverunner/waverunner.py index 402930fd..78397a73 100755 --- a/capture/scopes/waverunner/waverunner.py +++ b/capture/scopes/waverunner/waverunner.py @@ -1,7 +1,6 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - """Support for capturing traces using LeCroy WaveRunner 9104.""" import re @@ -140,14 +139,16 @@ def _get_and_print_cmd_error(self): print("WAVERUNNER: ERROR in last command: " + return_msg) def _fetch_file_from_scope(self, file_name_scope): - fetched_file = self._ask("TRANSFER_FILE? DISK,HDD,FILE," + file_name_scope) + fetched_file = self._ask("TRANSFER_FILE? DISK,HDD,FILE," + + file_name_scope) # remove echoed command characters from beginning fetched_file = fetched_file[5:] self._get_and_print_cmd_error() return fetched_file def _write_to_file_on_scope(self, file_name_scope, data): - self._write("TRANSFER_FILE DISK,HDD,FILE," + file_name_scope + "," + data) + self._write("TRANSFER_FILE DISK,HDD,FILE," + file_name_scope + "," + + data) self._get_and_print_cmd_error() def _delete_file_on_scope(self, file_name_scope): @@ -184,8 +185,7 @@ def load_setup_from_local_file(self, file_name_local): def _populate_device_info(self): manufacturer, model, serial, version = re.match( - "([^,]*),([^,]*),([^,]*),([^,]*)", self._ask("*IDN?") - ).groups() + "([^,]*),([^,]*),([^,]*),([^,]*)", self._ask("*IDN?")).groups() opts = ", ".join(self._ask("*OPT?").split(",")) self._device_info = { "manufacturer": manufacturer, @@ -196,18 +196,16 @@ def _populate_device_info(self): } def _print_device_info(self): + def print_info(manufacturer, model, serial, version, opts): # TODO: logging - print( - f"WAVERUNNER: Connected to {manufacturer} {model} (ip: " - f"{self._ip_addr}, serial: {serial}, " - f"version: {version}, options: {opts})" - ) + print(f"WAVERUNNER: Connected to {manufacturer} {model} (ip: " + f"{self._ip_addr}, serial: {serial}, " + f"version: {version}, options: {opts})") if opts == "WARNING : CURRENT REMOTE CONTROL INTERFACE IS TCPIP": print( "ERROR: WAVERUNNER: Must set remote control to VXI11 on scope under: " - "Utilities > Utilities Setup > Remote" - ) + "Utilities > Utilities Setup > Remote") print_info(**self._device_info) @@ -296,7 +294,8 @@ def _configure_acqu(self): # Note this is a default configuration and might not be meaningfull always # Only use channels 2 and 3 to maximize sampling rate. self._write("vbs 'app.Acquisition.Horizontal.ActiveChannels = \"2\"'") - self._write("vbs 'app.Acquisition.Horizontal.Maximize = \"FixedSampleRate\"'") + self._write( + "vbs 'app.Acquisition.Horizontal.Maximize = \"FixedSampleRate\"'") self._write("vbs 'app.Acquisition.Horizontal.SampleRate = \"1 GS/s\"'") def _configure_waveform_transfer(self): @@ -314,15 +313,13 @@ def _configure_waveform_transfer(self): ] self._write(";".join(commands)) - def configure_waveform_transfer_general( - self, num_segments, sparsing, num_samples, first_point, acqu_channel - ): + def configure_waveform_transfer_general(self, num_segments, sparsing, + num_samples, first_point, + acqu_channel): """Configures the oscilloscope for acqu with given parameters.""" - print( - f"WAVERUNNER: Configuring with num_segments={num_segments}, " - f"sparsing={sparsing}, num_samples={num_samples}, " - f"first_point={first_point}, acqu_channel=" + acqu_channel - ) + print(f"WAVERUNNER: Configuring with num_segments={num_segments}, " + f"sparsing={sparsing}, num_samples={num_samples}, " + f"first_point={first_point}, acqu_channel=" + acqu_channel) self.num_samples = num_samples self.num_segments = num_segments self.acqu_channel = acqu_channel @@ -389,14 +386,13 @@ def _parse_waveform(self, data): waves = np.frombuffer(data, dtype, len_, 22) # Reshape waves = waves.reshape( - (self.num_segments, int(waves.shape[0] / self.num_segments)) - ) + (self.num_segments, int(waves.shape[0] / self.num_segments))) if waves.shape[1] != self.num_samples: print( "WAVERUNNER: ERROR: scope returned too many samples per trace or segment" ) # Truncate, but means scope returns more samples than expected! - waves = waves[:, 0: self.num_samples] + waves = waves[:, 0:self.num_samples] return waves def capture_and_transfer_waves(self): diff --git a/ci/ci_trace_check/ci_compare_aes_traces.py b/ci/ci_trace_check/ci_compare_aes_traces.py index dd4b0105..c42764c7 100755 --- a/ci/ci_trace_check/ci_compare_aes_traces.py +++ b/ci/ci_trace_check/ci_compare_aes_traces.py @@ -72,14 +72,15 @@ def parse_args(): parser = argparse.ArgumentParser( description="""Calculate Pearson correlation between golden traces and captured traces. Failes when correlation - coefficient is below user threshold.""" - ) - parser.add_argument( - "-f", "--file_proj", required=True, help="chipwhisperer project file" - ) - parser.add_argument( - "-g", "--file_gold_proj", required=True, help="chipwhisperergolden project file" - ) + coefficient is below user threshold.""") + parser.add_argument("-f", + "--file_proj", + required=True, + help="chipwhisperer project file") + parser.add_argument("-g", + "--file_gold_proj", + required=True, + help="chipwhisperergolden project file") parser.add_argument( "-c", "--corr_coeff", diff --git a/cw/capture.py b/cw/capture.py index ec1e3f3f..2f3394a9 100755 --- a/cw/capture.py +++ b/cw/capture.py @@ -39,14 +39,12 @@ class ScopeType(str, Enum): app.add_typer(app_capture, name="capture", help="Capture traces for SCA") # Shared options for capture commands opt_force_program_bitstream = typer.Option( - None, help=("Force program FPGA with the bitstream.") -) + None, help=("Force program FPGA with the bitstream.")) opt_num_traces = typer.Option(None, help="Number of traces to capture.") opt_plot_traces = typer.Option(None, help="Number of traces to plot.") opt_scope_type = typer.Option(ScopeType.cw, help=("Scope type")) opt_ciphertexts_store = typer.Option( - False, help=("Store all ciphertexts for batch capture.") -) + False, help=("Store all ciphertexts for batch capture.")) def create_waverunner(ot, capture_cfg): @@ -85,7 +83,8 @@ def abort_handler(project, sig, frame): sys.exit(0) -def save_metadata(project, device_cfg, capture_cfg, trigger_cycles, sample_rate): +def save_metadata(project, device_cfg, capture_cfg, trigger_cycles, + sample_rate): # Save metadata to project file if sample_rate is not None: project.settingsDict["sample_rate"] = sample_rate @@ -98,7 +97,8 @@ def save_metadata(project, device_cfg, capture_cfg, trigger_cycles, sample_rate) # store last number of cycles where the trigger signal was high to metadata if trigger_cycles is not None: project.settingsDict["samples_trigger_high"] = trigger_cycles - project.settingsDict["datetime"] = datetime.now().strftime("%m/%d/%Y, %H:%M:%S") + project.settingsDict["datetime"] = datetime.now().strftime( + "%m/%d/%Y, %H:%M:%S") # Note: initialize_capture and plot_results are also used by other scripts. @@ -123,7 +123,8 @@ def initialize_capture(device_cfg, capture_cfg): ping_cnt = 0 while not version: if ping_cnt == 3: - raise RuntimeError(f"No response from the target (attempts: {ping_cnt}).") + raise RuntimeError( + f"No response from the target (attempts: {ping_cnt}).") ot.target.write("v" + "\n") ping_cnt += 1 time.sleep(0.5) @@ -136,24 +137,12 @@ def check_range(waves, bits_per_sample): """The ADC output is in the interval [0, 2**bits_per_sample-1]. Check that the recorded traces are within [1, 2**bits_per_sample-2] to ensure the ADC doesn't saturate.""" adc_range = np.array([0, 2**bits_per_sample]) - if not ( - np.all(np.greater(waves[:], adc_range[0])) and - np.all(np.less(waves[:], adc_range[1] - 1)) - ): - print( - "\nWARNING: Some samples are outside the range [" + - str(adc_range[0] + 1) + - ", " + - str(adc_range[1] - 2) + - "]." - ) - print( - "The ADC has a max range of [" + - str(adc_range[0]) + - ", " + - str(adc_range[1] - 1) + - "] and might saturate." - ) + if not (np.all(np.greater(waves[:], adc_range[0])) and + np.all(np.less(waves[:], adc_range[1] - 1))): + print("\nWARNING: Some samples are outside the range [" + + str(adc_range[0] + 1) + ", " + str(adc_range[1] - 2) + "].") + print("The ADC has a max range of [" + str(adc_range[0]) + ", " + + str(adc_range[1] - 1) + "] and might saturate.") print("It is recommended to reduce the scope gain (see device.py).") @@ -165,13 +154,10 @@ def plot_results(plot_cfg, project_name): print("Project contains no traces. Did the capture fail?") return - plot.save_plot_to_file( - project.waves, None, plot_cfg["num_traces"], plot_cfg["trace_image_filename"] - ) - print( - f'Created plot with {plot_cfg["num_traces"]} traces: ' - f'{Path(plot_cfg["trace_image_filename"]).resolve()}' - ) + plot.save_plot_to_file(project.waves, None, plot_cfg["num_traces"], + plot_cfg["trace_image_filename"]) + print(f'Created plot with {plot_cfg["num_traces"]} traces: ' + f'{Path(plot_cfg["trace_image_filename"]).resolve()}') @app.command() @@ -217,7 +203,8 @@ def capture_loop(trace_gen, ot, capture_cfg, device_cfg): # register ctrl-c handler to not lose already recorded traces if measurement is aborted signal.signal(signal.SIGINT, partial(abort_handler, project)) - for _ in tqdm(range(capture_cfg["num_traces"]), desc="Capturing", ncols=80): + for _ in tqdm(range(capture_cfg["num_traces"]), desc="Capturing", + ncols=80): traces = next(trace_gen) check_range(traces.wave, ot.scope.adc.bits_per_sample) project.traces.append(traces, dtype=np.uint16) @@ -242,52 +229,53 @@ def capture_aes_static(ot): Args: ot: Initialized OpenTitan target. """ - key = bytearray( - [ - 0x81, - 0x1E, - 0x37, - 0x31, - 0xB0, - 0x12, - 0x0A, - 0x78, - 0x42, - 0x78, - 0x1E, - 0x22, - 0xB2, - 0x5C, - 0xDD, - 0xF9, - ] - ) - text = bytearray( - [ - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - ] - ) + key = bytearray([ + 0x81, + 0x1E, + 0x37, + 0x31, + 0xB0, + 0x12, + 0x0A, + 0x78, + 0x42, + 0x78, + 0x1E, + 0x22, + 0xB2, + 0x5C, + 0xDD, + 0xF9, + ]) + text = bytearray([ + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + ]) tqdm.write(f"Fixed key: {binascii.b2a_hex(bytes(key))}") while True: cipher = AES.new(bytes(key), AES.MODE_ECB) - ret = cw.capture_trace(ot.scope, ot.target, text, key, ack=False, as_int=True) + ret = cw.capture_trace(ot.scope, + ot.target, + text, + key, + ack=False, + as_int=True) if not ret: raise RuntimeError("Capture failed.") expected = binascii.b2a_hex(cipher.encrypt(bytes(text))) @@ -332,7 +320,12 @@ def capture_aes_random(ot, ktp): ot.target.simpleserial_write("t", bytearray([0x00])) while True: _, text = ktp.next() - ret = cw.capture_trace(ot.scope, ot.target, text, key, ack=False, as_int=True) + ret = cw.capture_trace(ot.scope, + ot.target, + text, + key, + ack=False, + as_int=True) if not ret: raise RuntimeError("Capture failed.") expected = binascii.b2a_hex(cipher.encrypt(bytes(text))) @@ -377,19 +370,21 @@ def optimize_cw_capture(project, num_segments_storage): # - https://github.com/newaetech/chipwhisperer/issues/344 if num_segments_storage != len(project.segments): if num_segments_storage >= 2: - project.traces.tm.setTraceSegmentStatus(num_segments_storage - 2, False) + project.traces.tm.setTraceSegmentStatus(num_segments_storage - 2, + False) num_segments_storage = len(project.segments) return num_segments_storage def check_ciphertext(ot, expected_last_ciphertext, ciphertext_len): """Check the first word of the last ciphertext in a batch to make sure we are in sync.""" - actual_last_ciphertext = ot.target.simpleserial_read("r", ciphertext_len, ack=False) - assert actual_last_ciphertext == expected_last_ciphertext[0:ciphertext_len], ( - f"Incorrect encryption result!\n" - f"actual: {actual_last_ciphertext}\n" - f"expected: {expected_last_ciphertext}" - ) + actual_last_ciphertext = ot.target.simpleserial_read("r", + ciphertext_len, + ack=False) + assert actual_last_ciphertext == expected_last_ciphertext[ + 0:ciphertext_len], (f"Incorrect encryption result!\n" + f"actual: {actual_last_ciphertext}\n" + f"expected: {expected_last_ciphertext}") def capture_aes_random_batch(ot, ktp, capture_cfg, scope_type, device_cfg): @@ -411,8 +406,7 @@ def capture_aes_random_batch(ot, ktp, capture_cfg, scope_type, device_cfg): ot.target.simpleserial_write("k", key) # Seed the target's PRNG ot.target.simpleserial_write( - "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little") - ) + "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little")) # Create the ChipWhisperer project. project = cw.create_project(capture_cfg["project_name"], overwrite=True) @@ -426,7 +420,8 @@ def capture_aes_random_batch(ot, ktp, capture_cfg, scope_type, device_cfg): # register ctrl-c handler to not lose already recorded traces if measurement is aborted signal.signal(signal.SIGINT, partial(abort_handler, project)) - with tqdm(total=rem_num_traces, desc="Capturing", ncols=80, unit=" traces") as pbar: + with tqdm(total=rem_num_traces, desc="Capturing", ncols=80, + unit=" traces") as pbar: while rem_num_traces > 0: # Determine the number of traces for this batch and arm the oscilloscope. scope.num_segments = min(rem_num_traces, scope.num_segments_max) @@ -434,8 +429,7 @@ def capture_aes_random_batch(ot, ktp, capture_cfg, scope_type, device_cfg): # Start batch encryption. ot.target.simpleserial_write( - "b", scope.num_segments_actual.to_bytes(4, "little") - ) + "b", scope.num_segments_actual.to_bytes(4, "little")) # Transfer traces waves = scope.capture_and_transfer_waves() @@ -444,20 +438,22 @@ def capture_aes_random_batch(ot, ktp, capture_cfg, scope_type, device_cfg): check_range(waves, ot.scope.adc.bits_per_sample) # Generate plaintexts and ciphertexts to compare with the batch encryption results. - plaintexts = [ktp.next()[1] for _ in range(scope.num_segments_actual)] + plaintexts = [ + ktp.next()[1] for _ in range(scope.num_segments_actual) + ] ciphertexts = [ - bytearray(c) - for c in scared.aes.base.encrypt( - np.asarray(plaintexts), np.asarray(key) - ) + bytearray(c) for c in scared.aes.base.encrypt( + np.asarray(plaintexts), np.asarray(key)) ] check_ciphertext(ot, ciphertexts[-1], 4) - num_segments_storage = optimize_cw_capture(project, num_segments_storage) + num_segments_storage = optimize_cw_capture(project, + num_segments_storage) # Add traces of this batch to the project. - for wave, plaintext, ciphertext in zip(waves, plaintexts, ciphertexts): + for wave, plaintext, ciphertext in zip(waves, plaintexts, + ciphertexts): project.traces.append( cw.common.traces.Trace(wave, plaintext, ciphertext, key), dtype=np.uint16, @@ -512,107 +508,97 @@ def capture_aes_fvsr_key(ot): Args: ot: Initialized OpenTitan target. """ - key_generation = bytearray( - [ - 0x12, - 0x34, - 0x56, - 0x78, - 0x9A, - 0xBC, - 0xDE, - 0xF1, - 0x23, - 0x45, - 0x67, - 0x89, - 0xAB, - 0xCD, - 0xE0, - 0xF0, - ] - ) + key_generation = bytearray([ + 0x12, + 0x34, + 0x56, + 0x78, + 0x9A, + 0xBC, + 0xDE, + 0xF1, + 0x23, + 0x45, + 0x67, + 0x89, + 0xAB, + 0xCD, + 0xE0, + 0xF0, + ]) cipher_gen = AES.new(bytes(key_generation), AES.MODE_ECB) - text_fixed = bytearray( - [ - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - ] - ) - text_random = bytearray( - [ - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - ] - ) - key_fixed = bytearray( - [ - 0x81, - 0x1E, - 0x37, - 0x31, - 0xB0, - 0x12, - 0x0A, - 0x78, - 0x42, - 0x78, - 0x1E, - 0x22, - 0xB2, - 0x5C, - 0xDD, - 0xF9, - ] - ) - key_random = bytearray( - [ - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - ] - ) + text_fixed = bytearray([ + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + ]) + text_random = bytearray([ + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + ]) + key_fixed = bytearray([ + 0x81, + 0x1E, + 0x37, + 0x31, + 0xB0, + 0x12, + 0x0A, + 0x78, + 0x42, + 0x78, + 0x1E, + 0x22, + 0xB2, + 0x5C, + 0xDD, + 0xF9, + ]) + key_random = bytearray([ + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + ]) tqdm.write(f"Fixed key: {binascii.b2a_hex(bytes(key_fixed))}") @@ -628,7 +614,12 @@ def capture_aes_fvsr_key(ot): sample_fixed = random.randint(0, 1) cipher = AES.new(bytes(key), AES.MODE_ECB) - ret = cw.capture_trace(ot.scope, ot.target, text, key, ack=False, as_int=True) + ret = cw.capture_trace(ot.scope, + ot.target, + text, + key, + ack=False, + as_int=True) if not ret: raise RuntimeError("Capture failed.") expected = binascii.b2a_hex(cipher.encrypt(bytes(text))) @@ -656,9 +647,8 @@ def aes_fvsr_key( capture_end(ctx.obj.cfg) -def capture_aes_fvsr_key_batch( - ot, ktp, capture_cfg, scope_type, gen_ciphertexts, device_cfg -): +def capture_aes_fvsr_key_batch(ot, ktp, capture_cfg, scope_type, + gen_ciphertexts, device_cfg): """A generator for capturing AES traces for fixed vs random key test in batch mode. The data collection method is based on the derived test requirements (DTR) for TVLA: https://www.rambus.com/wp-content/uploads/2015/08/TVLA-DTR-with-AES.pdf @@ -677,35 +667,33 @@ def capture_aes_fvsr_key_batch( # TODO: Replace this with a dedicated PRNG to avoid other packages breaking our code. random.seed(capture_cfg["batch_prng_seed"]) # Seed the target's PRNGs - ot.target.simpleserial_write("l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) ot.target.simpleserial_write( - "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little") - ) + "l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) + ot.target.simpleserial_write( + "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little")) # Set and transfer the fixed key. # Without the sleep statement, the CW305 seems to fail to configure the batch PRNG # seed and/or the fixed key and then gets completely out of sync. time.sleep(0.5) - key_fixed = bytearray( - [ - 0x81, - 0x1E, - 0x37, - 0x31, - 0xB0, - 0x12, - 0x0A, - 0x78, - 0x42, - 0x78, - 0x1E, - 0x22, - 0xB2, - 0x5C, - 0xDD, - 0xF9, - ] - ) + key_fixed = bytearray([ + 0x81, + 0x1E, + 0x37, + 0x31, + 0xB0, + 0x12, + 0x0A, + 0x78, + 0x42, + 0x78, + 0x1E, + 0x22, + 0xB2, + 0x5C, + 0xDD, + 0xF9, + ]) tqdm.write(f"Fixed key: {binascii.b2a_hex(bytes(key_fixed))}") ot.target.simpleserial_write("f", key_fixed) @@ -723,7 +711,8 @@ def capture_aes_fvsr_key_batch( # register ctrl-c handler to not lose already recorded traces if measurement is aborted signal.signal(signal.SIGINT, partial(abort_handler, project)) - with tqdm(total=rem_num_traces, desc="Capturing", ncols=80, unit=" traces") as pbar: + with tqdm(total=rem_num_traces, desc="Capturing", ncols=80, + unit=" traces") as pbar: while rem_num_traces > 0: # Determine the number of traces for this batch and arm the oscilloscope. scope.num_segments = min(rem_num_traces, scope.num_segments_max) @@ -734,12 +723,10 @@ def capture_aes_fvsr_key_batch( # when this script is getting waves from the scope. if is_first_batch: ot.target.simpleserial_write( - "g", scope.num_segments_actual.to_bytes(4, "little") - ) + "g", scope.num_segments_actual.to_bytes(4, "little")) is_first_batch = False ot.target.simpleserial_write( - "e", scope.num_segments_actual.to_bytes(4, "little") - ) + "e", scope.num_segments_actual.to_bytes(4, "little")) # Transfer traces. waves = scope.capture_and_transfer_waves() @@ -760,19 +747,20 @@ def capture_aes_fvsr_key_batch( keys.append(key) plaintexts.append(plaintext) if gen_ciphertexts: - ciphertext = np.asarray(scared.aes.base.encrypt(plaintext, key)) + ciphertext = np.asarray( + scared.aes.base.encrypt(plaintext, key)) ciphertexts.append(ciphertext) sample_fixed = plaintext[0] & 0x1 if gen_ciphertexts: expected_last_ciphertext = ciphertexts[-1] else: expected_last_ciphertext = np.asarray( - scared.aes.base.encrypt(plaintext, key) - ) + scared.aes.base.encrypt(plaintext, key)) check_ciphertext(ot, expected_last_ciphertext, 4) - num_segments_storage = optimize_cw_capture(project, num_segments_storage) + num_segments_storage = optimize_cw_capture(project, + num_segments_storage) # Add traces of this batch to the project. By default we don't store the ciphertexts as # generating them on the host as well as transferring them over from the target @@ -780,10 +768,10 @@ def capture_aes_fvsr_key_batch( # absolutely needed. if gen_ciphertexts: for wave, plaintext, ciphertext, key in zip( - waves, plaintexts, ciphertexts, keys - ): + waves, plaintexts, ciphertexts, keys): project.traces.append( - cw.common.traces.Trace(wave, plaintext, ciphertext, key), + cw.common.traces.Trace(wave, plaintext, ciphertext, + key), dtype=np.uint16, ) else: @@ -886,14 +874,18 @@ def capture_sha3_random(ot, ktp, capture_cfg): ack_ret = ot.target.simpleserial_wait_ack(5000) if ack_ret is None: - raise Exception("Batch mode acknowledge error: Device and host not in sync") + raise Exception( + "Batch mode acknowledge error: Device and host not in sync") tqdm.write("No key used, as we are doing sha3 hashing") while True: _, text = ktp.next() - ret = cw.capture_trace( - ot.scope, ot.target, text, key=None, ack=False, as_int=True - ) + ret = cw.capture_trace(ot.scope, + ot.target, + text, + key=None, + ack=False, + as_int=True) if not ret: raise RuntimeError("Capture failed.") sha3 = SHA3_256.new(text) @@ -928,29 +920,28 @@ def capture_sha3_fvsr_data_batch(ot, ktp, capture_cfg, scope_type, device_cfg): ack_ret = ot.target.simpleserial_wait_ack(5000) if ack_ret is None: - raise Exception("Batch mode acknowledge error: Device and host not in sync") + raise Exception( + "Batch mode acknowledge error: Device and host not in sync") # Value defined under Section 5.3 in the derived test requirements (DTR) for TVLA. - plaintext_fixed = bytearray( - [ - 0xDA, - 0x39, - 0xA3, - 0xEE, - 0x5E, - 0x6B, - 0x4B, - 0x0D, - 0x32, - 0x55, - 0xBF, - 0xEF, - 0x95, - 0x60, - 0x18, - 0x90, - ] - ) + plaintext_fixed = bytearray([ + 0xDA, + 0x39, + 0xA3, + 0xEE, + 0x5E, + 0x6B, + 0x4B, + 0x0D, + 0x32, + 0x55, + 0xBF, + 0xEF, + 0x95, + 0x60, + 0x18, + 0x90, + ]) # Note that - at least on FPGA - the DTR value above may lead to "fake" leakage as for the # fixed trace set, the number of bits set in the first (37) and second 64-bit word (31), as @@ -974,10 +965,10 @@ def capture_sha3_fvsr_data_batch(ot, ktp, capture_cfg, scope_type, device_cfg): plaintext = plaintext_fixed random.seed(capture_cfg["batch_prng_seed"]) - ot.target.simpleserial_write("l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) ot.target.simpleserial_write( - "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little") - ) + "l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) + ot.target.simpleserial_write( + "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little")) # Create the ChipWhisperer project. project_file = capture_cfg["project_name"] @@ -992,7 +983,8 @@ def capture_sha3_fvsr_data_batch(ot, ktp, capture_cfg, scope_type, device_cfg): # register ctrl-c handler to not lose already recorded traces if measurement is aborted signal.signal(signal.SIGINT, partial(abort_handler, project)) - with tqdm(total=rem_num_traces, desc="Capturing", ncols=80, unit=" traces") as pbar: + with tqdm(total=rem_num_traces, desc="Capturing", ncols=80, + unit=" traces") as pbar: while rem_num_traces > 0: # Determine the number of traces for this batch and arm the oscilloscope. scope.num_segments = min(rem_num_traces, scope.num_segments_max) @@ -1000,8 +992,7 @@ def capture_sha3_fvsr_data_batch(ot, ktp, capture_cfg, scope_type, device_cfg): scope.arm() # Start batch encryption. ot.target.simpleserial_write( - "b", scope.num_segments_actual.to_bytes(4, "little") - ) + "b", scope.num_segments_actual.to_bytes(4, "little")) # This wait ist crucial to be in sync with the device ack_ret = ot.target.simpleserial_wait_ack(5000) if ack_ret is None: @@ -1027,11 +1018,8 @@ def capture_sha3_fvsr_data_batch(ot, ktp, capture_cfg, scope_type, device_cfg): sha3 = SHA3_256.new(plaintext) ciphertext = sha3.digest() - batch_digest = ( - ciphertext - if batch_digest is None - else bytes(a ^ b for (a, b) in zip(ciphertext, batch_digest)) - ) + batch_digest = (ciphertext if batch_digest is None else bytes( + a ^ b for (a, b) in zip(ciphertext, batch_digest))) plaintexts.append(plaintext) ciphertexts.append(binascii.b2a_hex(ciphertext)) sample_fixed = dummy_plaintext[0] & 1 @@ -1045,14 +1033,15 @@ def capture_sha3_fvsr_data_batch(ot, ktp, capture_cfg, scope_type, device_cfg): # Check the batch digest to make sure we are in sync. check_ciphertext(ot, batch_digest, 32) - num_segments_storage = optimize_cw_capture(project, num_segments_storage) + num_segments_storage = optimize_cw_capture(project, + num_segments_storage) # Add traces of this batch to the project. - for wave, plaintext, ciphertext in zip(waves, plaintexts, ciphertexts): + for wave, plaintext, ciphertext in zip(waves, plaintexts, + ciphertexts): project.traces.append( - cw.common.traces.Trace( - wave, plaintext, bytearray(ciphertext), None - ), + cw.common.traces.Trace(wave, plaintext, + bytearray(ciphertext), None), dtype=np.uint16, ) # Update the loop variable and the progress bar. @@ -1101,67 +1090,61 @@ def capture_sha3_fvsr_data(ot, capture_cfg): """ # we are using AES in ECB mode for generating random texts - key_generation = bytearray( - [ - 0x12, - 0x34, - 0x56, - 0x78, - 0x9A, - 0xBC, - 0xDE, - 0xF1, - 0x23, - 0x45, - 0x67, - 0x89, - 0xAB, - 0xCD, - 0xE0, - 0xF0, - ] - ) + key_generation = bytearray([ + 0x12, + 0x34, + 0x56, + 0x78, + 0x9A, + 0xBC, + 0xDE, + 0xF1, + 0x23, + 0x45, + 0x67, + 0x89, + 0xAB, + 0xCD, + 0xE0, + 0xF0, + ]) cipher = AES.new(bytes(key_generation), AES.MODE_ECB) - text_fixed = bytearray( - [ - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - ] - ) - text_random = bytearray( - [ - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - ] - ) + text_fixed = bytearray([ + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + ]) + text_random = bytearray([ + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + ]) sha3 = SHA3_256.new(text_fixed) digest_fixed = binascii.b2a_hex(sha3.digest()) @@ -1176,27 +1159,35 @@ def capture_sha3_fvsr_data(ot, capture_cfg): ack_ret = ot.target.simpleserial_wait_ack(5000) if ack_ret is None: - raise Exception("Batch mode acknowledge error: Device and host not in sync") + raise Exception( + "Batch mode acknowledge error: Device and host not in sync") tqdm.write("No key used, as we are doing sha3 hashing") - ot.target.simpleserial_write("l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) + ot.target.simpleserial_write( + "l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) # Start sampling with the fixed key. sample_fixed = 1 while True: if sample_fixed: - ret = cw.capture_trace( - ot.scope, ot.target, text_fixed, key=None, ack=False, as_int=True - ) + ret = cw.capture_trace(ot.scope, + ot.target, + text_fixed, + key=None, + ack=False, + as_int=True) if not ret: raise RuntimeError("Capture failed.") expected = digest_fixed got = binascii.b2a_hex(ret.textout) else: text_random = bytearray(cipher.encrypt(text_random)) - ret = cw.capture_trace( - ot.scope, ot.target, text_random, key=None, ack=False, as_int=True - ) + ret = cw.capture_trace(ot.scope, + ot.target, + text_random, + key=None, + ack=False, + as_int=True) if not ret: raise RuntimeError("Capture failed.") sha3 = SHA3_256.new(text_random) @@ -1258,7 +1249,12 @@ def capture_kmac_random(ot, ktp): tqdm.write(f"Using key: {binascii.b2a_hex(bytes(key))}") while True: _, text = ktp.next() - ret = cw.capture_trace(ot.scope, ot.target, text, key, ack=False, as_int=True) + ret = cw.capture_trace(ot.scope, + ot.target, + text, + key, + ack=False, + as_int=True) if not ret: raise RuntimeError("Capture failed.") mac = KMAC128.new(key=key, mac_len=32) @@ -1287,33 +1283,31 @@ def capture_kmac_fvsr_key_batch(ot, ktp, capture_cfg, scope_type, device_cfg): scope_type: cw or waverunner as a scope for batch capture. """ - key_fixed = bytearray( - [ - 0x81, - 0x1E, - 0x37, - 0x31, - 0xB0, - 0x12, - 0x0A, - 0x78, - 0x42, - 0x78, - 0x1E, - 0x22, - 0xB2, - 0x5C, - 0xDD, - 0xF9, - ] - ) + key_fixed = bytearray([ + 0x81, + 0x1E, + 0x37, + 0x31, + 0xB0, + 0x12, + 0x0A, + 0x78, + 0x42, + 0x78, + 0x1E, + 0x22, + 0xB2, + 0x5C, + 0xDD, + 0xF9, + ]) ot.target.simpleserial_write("f", key_fixed) key = key_fixed random.seed(capture_cfg["batch_prng_seed"]) - ot.target.simpleserial_write("l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) ot.target.simpleserial_write( - "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little") - ) + "l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) + ot.target.simpleserial_write( + "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little")) # Create the ChipWhisperer project. project_file = capture_cfg["project_name"] @@ -1328,7 +1322,8 @@ def capture_kmac_fvsr_key_batch(ot, ktp, capture_cfg, scope_type, device_cfg): # register ctrl-c handler to not lose already recorded traces if measurement is aborted signal.signal(signal.SIGINT, partial(abort_handler, project)) - with tqdm(total=rem_num_traces, desc="Capturing", ncols=80, unit=" traces") as pbar: + with tqdm(total=rem_num_traces, desc="Capturing", ncols=80, + unit=" traces") as pbar: while rem_num_traces > 0: # Determine the number of traces for this batch and arm the oscilloscope. scope.num_segments = min(rem_num_traces, scope.num_segments_max) @@ -1336,8 +1331,7 @@ def capture_kmac_fvsr_key_batch(ot, ktp, capture_cfg, scope_type, device_cfg): scope.arm() # Start batch encryption. ot.target.simpleserial_write( - "b", scope.num_segments_actual.to_bytes(4, "little") - ) + "b", scope.num_segments_actual.to_bytes(4, "little")) plaintexts = [] ciphertexts = [] @@ -1357,11 +1351,8 @@ def capture_kmac_fvsr_key_batch(ot, ktp, capture_cfg, scope_type, device_cfg): mac = KMAC128.new(key=key, mac_len=32) mac.update(plaintext) ciphertext = bytearray.fromhex(mac.hexdigest()) - batch_digest = ( - ciphertext - if batch_digest is None - else bytes(a ^ b for (a, b) in zip(ciphertext, batch_digest)) - ) + batch_digest = (ciphertext if batch_digest is None else bytes( + a ^ b for (a, b) in zip(ciphertext, batch_digest))) plaintexts.append(plaintext) ciphertexts.append(binascii.b2a_hex(ciphertext)) keys.append(key) @@ -1376,14 +1367,15 @@ def capture_kmac_fvsr_key_batch(ot, ktp, capture_cfg, scope_type, device_cfg): # Check the batch digest to make sure we are in sync. check_ciphertext(ot, batch_digest, 32) - num_segments_storage = optimize_cw_capture(project, num_segments_storage) + num_segments_storage = optimize_cw_capture(project, + num_segments_storage) # Add traces of this batch to the project. for wave, plaintext, ciphertext, key in zip( - waves, plaintexts, ciphertexts, keys - ): + waves, plaintexts, ciphertexts, keys): project.traces.append( - cw.common.traces.Trace(wave, plaintext, bytearray(ciphertext), key), + cw.common.traces.Trace(wave, plaintext, + bytearray(ciphertext), key), dtype=np.uint16, ) # Update the loop variable and the progress bar. @@ -1433,119 +1425,113 @@ def capture_kmac_fvsr_key(ot, capture_cfg): ot: Initialized OpenTitan target. """ - key_generation = bytearray( - [ - 0x12, - 0x34, - 0x56, - 0x78, - 0x9A, - 0xBC, - 0xDE, - 0xF1, - 0x23, - 0x45, - 0x67, - 0x89, - 0xAB, - 0xCD, - 0xE0, - 0xF0, - ] - ) + key_generation = bytearray([ + 0x12, + 0x34, + 0x56, + 0x78, + 0x9A, + 0xBC, + 0xDE, + 0xF1, + 0x23, + 0x45, + 0x67, + 0x89, + 0xAB, + 0xCD, + 0xE0, + 0xF0, + ]) cipher = AES.new(bytes(key_generation), AES.MODE_ECB) - text_fixed = bytearray( - [ - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - 0xAA, - ] - ) - text_random = bytearray( - [ - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - 0xCC, - ] - ) - key_fixed = bytearray( - [ - 0x81, - 0x1E, - 0x37, - 0x31, - 0xB0, - 0x12, - 0x0A, - 0x78, - 0x42, - 0x78, - 0x1E, - 0x22, - 0xB2, - 0x5C, - 0xDD, - 0xF9, - ] - ) - key_random = bytearray( - [ - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - 0x53, - ] - ) + text_fixed = bytearray([ + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + ]) + text_random = bytearray([ + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + 0xCC, + ]) + key_fixed = bytearray([ + 0x81, + 0x1E, + 0x37, + 0x31, + 0xB0, + 0x12, + 0x0A, + 0x78, + 0x42, + 0x78, + 0x1E, + 0x22, + 0xB2, + 0x5C, + 0xDD, + 0xF9, + ]) + key_random = bytearray([ + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + 0x53, + ]) tqdm.write(f"Using fixed key: {binascii.b2a_hex(bytes(key_fixed))}") - ot.target.simpleserial_write("l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) + ot.target.simpleserial_write( + "l", capture_cfg["lfsr_seed"].to_bytes(4, "little")) # Start sampling with the fixed key. sample_fixed = 1 while True: if sample_fixed: text_fixed = bytearray(cipher.encrypt(text_fixed)) - ret = cw.capture_trace( - ot.scope, ot.target, text_fixed, key_fixed, ack=False, as_int=True - ) + ret = cw.capture_trace(ot.scope, + ot.target, + text_fixed, + key_fixed, + ack=False, + as_int=True) if not ret: raise RuntimeError("Capture failed.") mac = KMAC128.new(key=key_fixed, mac_len=32) @@ -1556,9 +1542,12 @@ def capture_kmac_fvsr_key(ot, capture_cfg): else: text_random = bytearray(cipher.encrypt(text_random)) key_random = bytearray(cipher.encrypt(key_random)) - ret = cw.capture_trace( - ot.scope, ot.target, text_random, key_random, ack=False, as_int=True - ) + ret = cw.capture_trace(ot.scope, + ot.target, + text_random, + key_random, + ack=False, + as_int=True) if not ret: raise RuntimeError("Capture failed.") mac = KMAC128.new(key=key_random, mac_len=32) @@ -1610,7 +1599,8 @@ def kmac_fvsr_key_batch( capture_end(ctx.obj.cfg) -def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cfg): +def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, + device_cfg): """Capture traces for ECDSA P256/P384 secret key generation and modular inverse computation. @@ -1652,15 +1642,15 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf # Initialize some curve-dependent parameters. if capture_cfg["curve"] == "p256": curve_order_n = ( - 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 - ) + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551) key_bytes = 256 // 8 seed_bytes = 320 // 8 modinv_share_bytes = 320 // 8 modinv_mask_bytes = 128 // 8 else: # TODO: add support for P384 - raise NotImplementedError(f'Curve {capture_cfg["curve"]} is not supported') + raise NotImplementedError( + f'Curve {capture_cfg["curve"]} is not supported') # register ctrl-c handler to not lose already recorded traces if measurement is aborted signal.signal(signal.SIGINT, partial(abort_handler, project)) @@ -1671,8 +1661,7 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf if ktp.keyLen() != seed_bytes: raise ValueError( f"Unexpected seed length: {ktp.keyLen()}.\n" - f"Hint: set key len={seed_bytes} in the configuration file." - ) + f"Hint: set key len={seed_bytes} in the configuration file.") if ktp.textLen() != seed_bytes: raise ValueError( f"Unexpected mask length: {ktp.textLen()}.\n" @@ -1697,8 +1686,7 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf C = ktp.next_key() if len(C) != seed_bytes: raise ValueError( - f"Fixed seed length is {len(C)}, expected {seed_bytes}" - ) + f"Fixed seed length is {len(C)}, expected {seed_bytes}") ktp.key_len = key_bytes fixed_number = ktp.next_key() if len(fixed_number) != key_bytes: @@ -1707,10 +1695,11 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf ) ktp.key_len = seed_bytes - seed_fixed_int = int.from_bytes(C, byteorder="little") + int.from_bytes( - fixed_number, byteorder="little" - ) - seed_fixed = seed_fixed_int.to_bytes(seed_bytes, byteorder="little") + seed_fixed_int = int.from_bytes( + C, byteorder="little") + int.from_bytes(fixed_number, + byteorder="little") + seed_fixed = seed_fixed_int.to_bytes(seed_bytes, + byteorder="little") else: # In fixed-vs-random SEED mode we use only one fixed constant: # 1. seed_fixed - A 320 bit constant used to derive the fixed key @@ -1725,19 +1714,21 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf # Expected key is `seed mod n`, where n is the order of the curve and # `seed` is interpreted as little-endian. - expected_fixed_key = ( - int.from_bytes(seed_fixed, byteorder="little") % curve_order_n - ) + expected_fixed_key = (int.from_bytes(seed_fixed, byteorder="little") % + curve_order_n) sample_fixed = 1 # Loop to collect each power trace - for _ in tqdm(range(capture_cfg["num_traces"]), desc="Capturing", ncols=80): + for _ in tqdm(range(capture_cfg["num_traces"]), + desc="Capturing", + ncols=80): ot.scope.adc.offset = ot.offset_samples if capture_cfg["masks_off"] is True: # Use a constant mask for each trace - mask = bytearray(capture_cfg["plain_text_len_bytes"]) # all zeros + mask = bytearray( + capture_cfg["plain_text_len_bytes"]) # all zeros else: # Generate a new random mask for each trace. mask = ktp.next_text() @@ -1761,12 +1752,13 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf random_number = ktp.next_key() ktp.key_len = seed_bytes seed_used_int = int.from_bytes( - C, byteorder="little" - ) + int.from_bytes(random_number, byteorder="little") - seed_used = seed_used_int.to_bytes(seed_bytes, byteorder="little") + C, byteorder="little") + int.from_bytes( + random_number, byteorder="little") + seed_used = seed_used_int.to_bytes(seed_bytes, + byteorder="little") expected_key = ( - int.from_bytes(seed_used, byteorder="little") % curve_order_n - ) + int.from_bytes(seed_used, byteorder="little") % + curve_order_n) else: # In fixed-vs-random SEED mode, the fixed set of measurements is # generated using the fixed 320 bit seed. The random set of @@ -1779,8 +1771,8 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf else: seed_used = ktp.next_key() expected_key = ( - int.from_bytes(seed_used, byteorder="little") % curve_order_n - ) + int.from_bytes(seed_used, byteorder="little") % + curve_order_n) # Decide for next round if we use the fixed or a random seed. sample_fixed = random.randint(0, 1) @@ -1831,11 +1823,9 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf tqdm.write(f"share1 = {share1.hex()}") if actual_key != expected_key: - raise RuntimeError( - "Bad generated key:\n" - f"Expected: {hex(expected_key)}\n" - f"Actual: {hex(actual_key)}" - ) + raise RuntimeError("Bad generated key:\n" + f"Expected: {hex(expected_key)}\n" + f"Actual: {hex(actual_key)}") # Create a chipwhisperer trace object and save it to the project # Args/fields of Trace object: waves, textin, textout, key @@ -1865,10 +1855,8 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf # you want a random fixed key or a hardcoded fixed key) # k_fixed_barray = ktp.next_key()[:256] k_fixed_barray = bytearray( - ( - 0x2648D0D248B70944DFD84C2F85EA5793729112E7CAFA50ABDF7EF8B7594FA2A1 - ).to_bytes(key_bytes, "little") - ) + (0x2648D0D248B70944DFD84C2F85EA5793729112E7CAFA50ABDF7EF8B7594FA2A1 + ).to_bytes(key_bytes, "little")) k_fixed = int.from_bytes(k_fixed_barray, byteorder="little") print("Fixed input:") @@ -1879,7 +1867,9 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf sample_fixed = 1 # Loop to collect each power trace - for _ in tqdm(range(capture_cfg["num_traces"]), desc="Capturing", ncols=80): + for _ in tqdm(range(capture_cfg["num_traces"]), + desc="Capturing", + ncols=80): ot.scope.adc.offset = ot.offset_samples @@ -1897,8 +1887,7 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf if k1_fixed >= pow(2, 320): k1_fixed -= curve_order_n input_k1_fixed = bytearray( - (k1_fixed).to_bytes(modinv_share_bytes, "little") - ) + (k1_fixed).to_bytes(modinv_share_bytes, "little")) # Use the fixed input. input_k0_used = input_k0_fixed input_k1_used = input_k1_fixed @@ -1909,14 +1898,17 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf input_k0_used = ktp.next_key() input_k1_used = ktp.next_key() # calculate the key from the shares - k_used = ( - int.from_bytes(input_k0_used, byteorder="little") + - int.from_bytes(input_k1_used, byteorder="little") - ) % curve_order_n + k_used = (int.from_bytes(input_k0_used, byteorder="little") + + int.from_bytes(input_k1_used, + byteorder="little")) % curve_order_n expected_output = pow(k_used, -1, curve_order_n) - tqdm.write(f'k0 = {hex(int.from_bytes(input_k0_used, byteorder="little"))}') - tqdm.write(f'k1 = {hex(int.from_bytes(input_k1_used, byteorder="little"))}') + tqdm.write( + f'k0 = {hex(int.from_bytes(input_k0_used, byteorder="little"))}' + ) + tqdm.write( + f'k1 = {hex(int.from_bytes(input_k1_used, byteorder="little"))}' + ) # Decide for next round if we use the fixed or a random seed. sample_fixed = random.randint(0, 1) @@ -1942,25 +1934,23 @@ def capture_otbn_vertical(ot, ktp, fw_bin, pll_frequency, capture_cfg, device_cf kalpha_inv = ot.target.simpleserial_read("r", key_bytes, ack=False) if kalpha_inv is None: raise RuntimeError("Modinv device output (k*alpha)^-1 is none") - alpha = ot.target.simpleserial_read("r", modinv_mask_bytes, ack=False) + alpha = ot.target.simpleserial_read("r", + modinv_mask_bytes, + ack=False) if alpha is None: raise RuntimeError("Modinv device output alpha is none") # Actual result (kalpha_inv*alpha) mod n: - actual_output = ( - int.from_bytes(kalpha_inv, byteorder="little") * - int.from_bytes(alpha, byteorder="little") % - curve_order_n - ) + actual_output = (int.from_bytes(kalpha_inv, byteorder="little") * + int.from_bytes(alpha, byteorder="little") % + curve_order_n) tqdm.write(f"k^-1 = {hex(actual_output)}\n") if actual_output != expected_output: - raise RuntimeError( - "Bad computed modinv output:\n" - f"Expected: {hex(expected_output)}\n" - f"Actual: {hex(actual_output)}" - ) + raise RuntimeError("Bad computed modinv output:\n" + f"Expected: {hex(expected_output)}\n" + f"Actual: {hex(actual_output)}") # Create a chipwhisperer trace object and save it to the project # Args/fields of Trace object: waves, textin, textout, key @@ -2008,7 +1998,9 @@ def otbn_vertical( print( f'Target setup with clock frequency {ctx.obj.cfg["device"]["pll_frequency"] / 1000000} MHz' ) - print(f"Scope setup with sampling rate {ctx.obj.ot.scope.clock.adc_freq} S/s") + print( + f"Scope setup with sampling rate {ctx.obj.ot.scope.clock.adc_freq} S/s" + ) # Call the capture loop capture_otbn_vertical( @@ -2056,13 +2048,13 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): # Initialize some curve-dependent parameters. if capture_cfg["curve"] == "p256": curve_order_n = ( - 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 - ) + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551) key_bytes = 256 // 8 seed_bytes = 320 // 8 else: # TODO: add support for P384 - raise NotImplementedError(f'Curve {capture_cfg["curve"]} is not supported') + raise NotImplementedError( + f'Curve {capture_cfg["curve"]} is not supported') # Capture traces. rem_num_traces = capture_cfg["num_traces"] @@ -2083,7 +2075,9 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): scope._scope.adc.decimate = capture_cfg["decimate"] # Print final scope parameter - print(f"Scope setup with final sampling rate of {scope._scope.clock.adc_freq} S/s") + print( + f"Scope setup with final sampling rate of {scope._scope.clock.adc_freq} S/s" + ) # register ctrl-c handler to not lose already recorded traces if measurement is aborted signal.signal(signal.SIGINT, partial(abort_handler, project)) @@ -2094,8 +2088,7 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): if ktp.keyLen() != seed_bytes: raise ValueError( f"Unexpected seed length: {ktp.keyLen()}.\n" - f"Hint: set key len={seed_bytes} in the configuration file." - ) + f"Hint: set key len={seed_bytes} in the configuration file.") if ktp.textLen() != seed_bytes: raise ValueError( f"Unexpected mask length: {ktp.textLen()}.\n" @@ -2110,8 +2103,7 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): # running this function with the same batch_prng_seed random.seed(capture_cfg["batch_prng_seed"]) ot.target.simpleserial_write( - "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little") - ) + "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little")) time.sleep(0.3) # Generate fixed constants for all traces of the keygen operation. @@ -2126,8 +2118,7 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): C = ktp.next_key() if len(C) != seed_bytes: raise ValueError( - f"Fixed seed length is {len(C)}, expected {seed_bytes}" - ) + f"Fixed seed length is {len(C)}, expected {seed_bytes}") ktp.key_len = key_bytes fixed_number = ktp.next_key() if len(fixed_number) != key_bytes: @@ -2136,8 +2127,10 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): ) ktp.key_len = seed_bytes C_int = int.from_bytes(C, byteorder="little") - seed_fixed_int = C_int + int.from_bytes(fixed_number, byteorder="little") - seed_fixed = seed_fixed_int.to_bytes(seed_bytes, byteorder="little") + seed_fixed_int = C_int + int.from_bytes(fixed_number, + byteorder="little") + seed_fixed = seed_fixed_int.to_bytes(seed_bytes, + byteorder="little") print("Constant redundancy:") print(binascii.b2a_hex(C)) @@ -2173,27 +2166,26 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): if capture_cfg["test_type"] == "KEY": random.seed(capture_cfg["batch_prng_seed"]) ot.target.simpleserial_write( - "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little") - ) + "s", capture_cfg["batch_prng_seed"].to_bytes(4, "little")) time.sleep(0.3) - with tqdm( - total=rem_num_traces, desc="Capturing", ncols=80, unit=" traces" - ) as pbar: + with tqdm(total=rem_num_traces, + desc="Capturing", + ncols=80, + unit=" traces") as pbar: while rem_num_traces > 0: # Determine the number of traces for this batch and arm the oscilloscope. - scope.num_segments = min(rem_num_traces, scope.num_segments_max) + scope.num_segments = min(rem_num_traces, + scope.num_segments_max) scope.arm() # Start batch keygen if capture_cfg["test_type"] == "KEY": ot.target.simpleserial_write( - "e", scope.num_segments_actual.to_bytes(4, "little") - ) + "e", scope.num_segments_actual.to_bytes(4, "little")) else: ot.target.simpleserial_write( - "b", scope.num_segments_actual.to_bytes(4, "little") - ) + "b", scope.num_segments_actual.to_bytes(4, "little")) # Transfer traces waves = scope.capture_and_transfer_waves() @@ -2221,11 +2213,9 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): random_number = ktp.next_key() ktp.key_len = seed_bytes seed_barray_int = C_int + int.from_bytes( - random_number, byteorder="little" - ) + random_number, byteorder="little") seed_barray = seed_barray_int.to_bytes( - seed_bytes, byteorder="little" - ) + seed_bytes, byteorder="little") seed = seed_barray_int else: if sample_fixed: @@ -2236,7 +2226,8 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): seed = int.from_bytes(seed_barray, "little") if capture_cfg["masks_off"] is True: - mask_barray = bytearray(capture_cfg["plain_text_len_bytes"]) + mask_barray = bytearray( + capture_cfg["plain_text_len_bytes"]) mask = int.from_bytes(mask_barray, "little") else: mask_barray = ktp.next_text() @@ -2269,12 +2260,12 @@ def capture_otbn_vertical_batch(ot, ktp, capture_cfg, scope_type, device_cfg): ) num_segments_storage = optimize_cw_capture( - project, num_segments_storage - ) + project, num_segments_storage) # Create a chipwhisperer trace object and save it to the project # Args/fields of Trace object: waves, textin, textout, key - for wave, seed, mask, d0, d1 in zip(waves, seeds, masks, d0s, d1s): + for wave, seed, mask, d0, d1 in zip(waves, seeds, masks, d0s, + d1s): d = d0 + d1 trace = cw.common.traces.Trace(wave, d, mask, seed) project.traces.append(trace, dtype=np.uint16) @@ -2319,9 +2310,8 @@ def otbn_vertical_batch( capture_end(ctx.obj.cfg) -def capture_ecdsa_sections( - ot, fw_bin, pll_frequency, num_sections, secret_k, priv_key_d, msg -): +def capture_ecdsa_sections(ot, fw_bin, pll_frequency, num_sections, secret_k, + priv_key_d, msg): """A utility function to collect the full OTBN trace section by section ECDSA is a long operation (e.g, ECDSA-256 takes ~7M samples) that doesn't fit @@ -2375,7 +2365,8 @@ def capture_ecdsa_sections( print("Observed number of cycles: %d" % cycles) # Append the section into the waves array - tmp_buffer = np.append(tmp_buffer, ot.scope.get_last_trace(as_int=True)) + tmp_buffer = np.append(tmp_buffer, + ot.scope.get_last_trace(as_int=True)) return tmp_buffer @@ -2424,7 +2415,8 @@ def capture_ecdsa_simple(ot, fw_bin, pll_frequency, capture_cfg): signal.signal(signal.SIGINT, partial(abort_handler, project)) # Loop to collect each power trace - for _ in tqdm(range(capture_cfg["num_traces"]), desc="Capturing", ncols=80): + for _ in tqdm(range(capture_cfg["num_traces"]), desc="Capturing", + ncols=80): # This part can be modified to create a new command. # For example, a random secret scalar can be set using the following @@ -2436,363 +2428,347 @@ def capture_ecdsa_simple(ot, fw_bin, pll_frequency, capture_cfg): # ECDSA-384 if capture_cfg["key_len_bytes"] == 48: # Set two shares of the private key d - priv_key_d0 = bytearray( - [ - 0x6B, - 0x9D, - 0x3D, - 0xAD, - 0x2E, - 0x1B, - 0x8C, - 0x1C, - 0x05, - 0xB1, - 0x98, - 0x75, - 0xB6, - 0x65, - 0x9F, - 0x4D, - 0xE2, - 0x3C, - 0x3B, - 0x66, - 0x7B, - 0xF2, - 0x97, - 0xBA, - 0x9A, - 0xA4, - 0x77, - 0x40, - 0x78, - 0x71, - 0x37, - 0xD8, - 0x96, - 0xD5, - 0x72, - 0x4E, - 0x4C, - 0x70, - 0xA8, - 0x25, - 0xF8, - 0x72, - 0xC9, - 0xEA, - 0x60, - 0xD2, - 0xED, - 0xF5, - ] - ) - priv_key_d1 = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + priv_key_d0 = bytearray([ + 0x6B, + 0x9D, + 0x3D, + 0xAD, + 0x2E, + 0x1B, + 0x8C, + 0x1C, + 0x05, + 0xB1, + 0x98, + 0x75, + 0xB6, + 0x65, + 0x9F, + 0x4D, + 0xE2, + 0x3C, + 0x3B, + 0x66, + 0x7B, + 0xF2, + 0x97, + 0xBA, + 0x9A, + 0xA4, + 0x77, + 0x40, + 0x78, + 0x71, + 0x37, + 0xD8, + 0x96, + 0xD5, + 0x72, + 0x4E, + 0x4C, + 0x70, + 0xA8, + 0x25, + 0xF8, + 0x72, + 0xC9, + 0xEA, + 0x60, + 0xD2, + 0xED, + 0xF5, + ]) + priv_key_d1 = bytearray([ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) # Set two shares of the scalar secret_k - secret_k0 = bytearray( - [ - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - secret_k1 = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + secret_k0 = bytearray([ + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) + secret_k1 = bytearray([ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) # ECDSA-256 elif capture_cfg["key_len_bytes"] == 32: # Set two shares of the private key d - priv_key_d0 = bytearray( - [ - 0xCD, - 0xB4, - 0x57, - 0xAF, - 0x1C, - 0x9F, - 0x4C, - 0x74, - 0x02, - 0x0C, - 0x7E, - 0x8B, - 0xE9, - 0x93, - 0x3E, - 0x28, - 0x0C, - 0xF0, - 0x18, - 0x0D, - 0xF4, - 0x6C, - 0x0B, - 0xDA, - 0x7A, - 0xBB, - 0xE6, - 0x8F, - 0xB7, - 0xA0, - 0x45, - 0x55, - ] - ) - priv_key_d1 = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + priv_key_d0 = bytearray([ + 0xCD, + 0xB4, + 0x57, + 0xAF, + 0x1C, + 0x9F, + 0x4C, + 0x74, + 0x02, + 0x0C, + 0x7E, + 0x8B, + 0xE9, + 0x93, + 0x3E, + 0x28, + 0x0C, + 0xF0, + 0x18, + 0x0D, + 0xF4, + 0x6C, + 0x0B, + 0xDA, + 0x7A, + 0xBB, + 0xE6, + 0x8F, + 0xB7, + 0xA0, + 0x45, + 0x55, + ]) + priv_key_d1 = bytearray([ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) # Set two shares of the scalar secret_k - secret_k0 = bytearray( - [ - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - secret_k1 = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + secret_k0 = bytearray([ + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) + secret_k1 = bytearray([ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) else: raise RuntimeError("priv_key_d must be either 32B or 48B") @@ -2802,18 +2778,17 @@ def capture_ecdsa_simple(ot, fw_bin, pll_frequency, capture_cfg): # Create a clean array to keep the collected traces waves = np.array([]) - waves = capture_ecdsa_sections( - ot, fw_bin, pll_frequency, num_sections, secret_k, priv_key_d, msg - ) + waves = capture_ecdsa_sections(ot, fw_bin, pll_frequency, num_sections, + secret_k, priv_key_d, msg) # Read 32 bytes of signature_r and signature_s back from the device - sig_r = ot.target.simpleserial_read( - "r", capture_cfg["output_len_bytes"], ack=False - ) + sig_r = ot.target.simpleserial_read("r", + capture_cfg["output_len_bytes"], + ack=False) print(f"sig_r = {''.join('{:02x}'.format(x) for x in sig_r)}") - sig_s = ot.target.simpleserial_read( - "r", capture_cfg["output_len_bytes"], ack=False - ) + sig_s = ot.target.simpleserial_read("r", + capture_cfg["output_len_bytes"], + ack=False) print(f"sig_s = {''.join('{:02x}'.format(x) for x in sig_s)}") # Create a chipwhisperer trace object and save it to the project @@ -2855,7 +2830,9 @@ def ecdsa_simple( print( f'Target setup with clock frequency {ctx.obj.cfg["device"]["pll_frequency"] / 1000000} MHz' ) - print(f"Scope setup with sampling rate {ctx.obj.ot.scope.clock.adc_freq} S/s") + print( + f"Scope setup with sampling rate {ctx.obj.ot.scope.clock.adc_freq} S/s" + ) # Call the capture loop capture_ecdsa_simple( @@ -2905,7 +2882,8 @@ def capture_ecdsa_stream(ot, fw_bin, pll_frequency, capture_cfg): signal.signal(signal.SIGINT, partial(abort_handler, project)) # Loop to collect traces - for _ in tqdm(range(capture_cfg["num_traces"]), desc="Capturing", ncols=80): + for _ in tqdm(range(capture_cfg["num_traces"]), desc="Capturing", + ncols=80): # This part can be modified to create a new command. # For example, a random secret scalar can be set using the following # from numpy.random import default_rng @@ -2916,363 +2894,347 @@ def capture_ecdsa_stream(ot, fw_bin, pll_frequency, capture_cfg): # ECDSA-384 if capture_cfg["key_len_bytes"] == 48: # Set two shares of the private key d - priv_key_d0 = bytearray( - [ - 0x6B, - 0x9D, - 0x3D, - 0xAD, - 0x2E, - 0x1B, - 0x8C, - 0x1C, - 0x05, - 0xB1, - 0x98, - 0x75, - 0xB6, - 0x65, - 0x9F, - 0x4D, - 0xE2, - 0x3C, - 0x3B, - 0x66, - 0x7B, - 0xF2, - 0x97, - 0xBA, - 0x9A, - 0xA4, - 0x77, - 0x40, - 0x78, - 0x71, - 0x37, - 0xD8, - 0x96, - 0xD5, - 0x72, - 0x4E, - 0x4C, - 0x70, - 0xA8, - 0x25, - 0xF8, - 0x72, - 0xC9, - 0xEA, - 0x60, - 0xD2, - 0xED, - 0xF5, - ] - ) - priv_key_d1 = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + priv_key_d0 = bytearray([ + 0x6B, + 0x9D, + 0x3D, + 0xAD, + 0x2E, + 0x1B, + 0x8C, + 0x1C, + 0x05, + 0xB1, + 0x98, + 0x75, + 0xB6, + 0x65, + 0x9F, + 0x4D, + 0xE2, + 0x3C, + 0x3B, + 0x66, + 0x7B, + 0xF2, + 0x97, + 0xBA, + 0x9A, + 0xA4, + 0x77, + 0x40, + 0x78, + 0x71, + 0x37, + 0xD8, + 0x96, + 0xD5, + 0x72, + 0x4E, + 0x4C, + 0x70, + 0xA8, + 0x25, + 0xF8, + 0x72, + 0xC9, + 0xEA, + 0x60, + 0xD2, + 0xED, + 0xF5, + ]) + priv_key_d1 = bytearray([ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) # Set two shares of the scalar secret_k - secret_k0 = bytearray( - [ - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - secret_k1 = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + secret_k0 = bytearray([ + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) + secret_k1 = bytearray([ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) # ECDSA-256 elif capture_cfg["key_len_bytes"] == 32: # Set two shares of the private key d - priv_key_d0 = bytearray( - [ - 0xCD, - 0xB4, - 0x57, - 0xAF, - 0x1C, - 0x9F, - 0x4C, - 0x74, - 0x02, - 0x0C, - 0x7E, - 0x8B, - 0xE9, - 0x93, - 0x3E, - 0x28, - 0x0C, - 0xF0, - 0x18, - 0x0D, - 0xF4, - 0x6C, - 0x0B, - 0xDA, - 0x7A, - 0xBB, - 0xE6, - 0x8F, - 0xB7, - 0xA0, - 0x45, - 0x55, - ] - ) - priv_key_d1 = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + priv_key_d0 = bytearray([ + 0xCD, + 0xB4, + 0x57, + 0xAF, + 0x1C, + 0x9F, + 0x4C, + 0x74, + 0x02, + 0x0C, + 0x7E, + 0x8B, + 0xE9, + 0x93, + 0x3E, + 0x28, + 0x0C, + 0xF0, + 0x18, + 0x0D, + 0xF4, + 0x6C, + 0x0B, + 0xDA, + 0x7A, + 0xBB, + 0xE6, + 0x8F, + 0xB7, + 0xA0, + 0x45, + 0x55, + ]) + priv_key_d1 = bytearray([ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) # Set two shares of the scalar secret_k - secret_k0 = bytearray( - [ - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) - secret_k1 = bytearray( - [ - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ] - ) + secret_k0 = bytearray([ + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) + secret_k1 = bytearray([ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) else: raise RuntimeError("priv_key_d must be either 32B or 48B") @@ -3313,13 +3275,13 @@ def capture_ecdsa_stream(ot, fw_bin, pll_frequency, capture_cfg): waves = np.append(waves, ot.scope.get_last_trace(as_int=True)) # Read signature_r and signature_s back from the device - sig_r = ot.target.simpleserial_read( - "r", capture_cfg["output_len_bytes"], ack=False - ) + sig_r = ot.target.simpleserial_read("r", + capture_cfg["output_len_bytes"], + ack=False) print(f"sig_r = {''.join('{:02x}'.format(x) for x in sig_r)}") - sig_s = ot.target.simpleserial_read( - "r", capture_cfg["output_len_bytes"], ack=False - ) + sig_s = ot.target.simpleserial_read("r", + capture_cfg["output_len_bytes"], + ack=False) print(f"sig_s = {''.join('{:02x}'.format(x) for x in sig_s)}") # Create a chipwhisperer trace object and save it to the project @@ -3359,7 +3321,9 @@ def ecdsa_stream( print( f'Target setup with clock frequency {ctx.obj.cfg["device"]["pll_frequency"] / 1000000} MHz' ) - print(f"Scope setup with sampling rate {ctx.obj.ot.scope.clock.adc_freq} S/s") + print( + f"Scope setup with sampling rate {ctx.obj.ot.scope.clock.adc_freq} S/s" + ) capture_ecdsa_stream( ctx.obj.ot, @@ -3376,7 +3340,8 @@ def plot_cmd(ctx: typer.Context, num_traces: int = opt_plot_traces): if num_traces is not None: ctx.obj.cfg["plot_capture"]["num_traces"] = num_traces - plot_results(ctx.obj.cfg["plot_capture"], ctx.obj.cfg["capture"]["project_name"]) + plot_results(ctx.obj.cfg["plot_capture"], + ctx.obj.cfg["capture"]["project_name"]) @app.callback() diff --git a/cw/mix_columns_cpa_attack.py b/cw/mix_columns_cpa_attack.py index 2876c7f1..0accd6ba 100755 --- a/cw/mix_columns_cpa_attack.py +++ b/cw/mix_columns_cpa_attack.py @@ -35,12 +35,7 @@ if known_key_bytes[i] == key_guess_bytes[i]: num_bytes_match += 1 print("FAILED: key_guess != known_key") - print( - " " + - str(num_bytes_match) + - "/" + - str(len(known_key_bytes)) + - " bytes guessed correctly." - ) + print(" " + str(num_bytes_match) + "/" + str(len(known_key_bytes)) + + " bytes guessed correctly.") else: print("SUCCESS!") diff --git a/cw/save_waverunner_config_to_file.py b/cw/save_waverunner_config_to_file.py index f3cd972d..bdcaad8c 100755 --- a/cw/save_waverunner_config_to_file.py +++ b/cw/save_waverunner_config_to_file.py @@ -2,7 +2,6 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - """Save WaveRunner to file for later use by script.""" from datetime import datetime diff --git a/fault_injection/fi_crypto.py b/fault_injection/fi_crypto.py index 7ee0a488..e986aeae 100755 --- a/fault_injection/fi_crypto.py +++ b/fault_injection/fi_crypto.py @@ -45,7 +45,7 @@ def bytes_to_words(byte_array): word_list = [] for i in range(0, len(byte_array), 4): - chunk = byte_array[i: i + 4] + chunk = byte_array[i:i + 4] word = struct.unpack(">I", chunk)[0] word_list.append(word) return word_list @@ -64,9 +64,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Create target config & setup target. logger.info(f"Initializing target {cfg['target']['target_type']} ...") @@ -80,7 +79,7 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["fisetup"].get("usb_serial"), + husky_serial=cfg["fisetup"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) @@ -111,20 +110,21 @@ def print_fi_statistic(fi_results: list) -> None: fi_results: The FI results. """ num_total = len(fi_results) - num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, 2) - num_exp = round((fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) - num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, 2) + num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, + 2) + num_exp = round( + (fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) + num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, + 2) logger.info( f"{num_total} faults, {fi_results.count(FISuccess.SUCCESS)}" f"({num_succ}%) successful, {fi_results.count(FISuccess.EXPRESPONSE)}" f"({num_exp}%) expected, and {fi_results.count(FISuccess.NORESPONSE)}" - f"({num_no}%) no response." - ) + f"({num_no}%) no response.") -def fi_parameter_sweep( - cfg: dict, target: Target, fi_gear, project: FIProject, ot_communication: OTFICrypto -) -> None: +def fi_parameter_sweep(cfg: dict, target: Target, fi_gear, project: FIProject, + ot_communication: OTFICrypto) -> None: """Fault parameter sweep. Sweep through the fault parameter space. @@ -150,15 +150,15 @@ def fi_parameter_sweep( cfg["test"]["core_config"], cfg["test"]["sensor_config"], cfg["test"]["alert_config"], - ) - ) + )) # Store results in array for a quick access. fi_results = [] # Start the parameter sweep. remaining_iterations = fi_gear.get_num_fault_injections() - with tqdm( - total=remaining_iterations, desc="Injecting", ncols=80, unit=" different faults" - ) as pbar: + with tqdm(total=remaining_iterations, + desc="Injecting", + ncols=80, + unit=" different faults") as pbar: while remaining_iterations > 0: # Get fault parameters (e.g., trigger delay, glitch voltage). fault_parameters = fi_gear.generate_fi_parameters() @@ -192,7 +192,8 @@ def fi_parameter_sweep( if "aes" in cfg["test"]["which_test"]: cipher_gen = AES.new(bytes(cfg["test"]["key"]), AES.MODE_ECB) expected_response = [ - x for x in cipher_gen.encrypt(bytes(cfg["test"]["plaintext"])) + x for x in cipher_gen.encrypt( + bytes(cfg["test"]["plaintext"])) ] exp_json = { "ciphertext": expected_response, @@ -239,7 +240,8 @@ def fi_parameter_sweep( logger.info("Error: Hash mode not recognized.") return hmac.update(bytes(cfg["test"]["msg"])) - expected_response = bytes_to_words(bytearray(hmac.digest())) + expected_response = bytes_to_words(bytearray( + hmac.digest())) expected_response.reverse() if cfg["test"]["hash_mode"] == 0: expected_response += [0] * 8 @@ -249,6 +251,7 @@ def fi_parameter_sweep( elif "kmac" in cfg["test"]["which_test"]: expected_response = '{"digest":[184,34,91,108,231,47,251,27], \ "err_status":0,"alerts":[0,0,0],"ast_alerts":[0,0]}' + exp_json = json.loads(expected_response) elif "shadow_reg_access" in cfg["test"]["which_test"]: expected_response = ( @@ -282,10 +285,12 @@ def fi_parameter_sweep( resp_json = json.loads(response_compare) if "alerts" in resp_json: del resp_json["alerts"] - response_compare = json.dumps(resp_json, separators=(",", ":")) + response_compare = json.dumps(resp_json, + separators=(",", ":")) if "alerts" in exp_json: del exp_json["alerts"] - expected_response = json.dumps(exp_json, separators=(",", ":")) + expected_response = json.dumps(exp_json, + separators=(",", ":")) # Check if result is expected result (FI failed) or unexpected # result (FI successful). @@ -325,7 +330,8 @@ def print_plot(project: FIProject, config: dict, file: Path) -> None: if config["fiproject"]["show_plot"]: plot.save_fi_plot_to_file(config, project, file) logger.info("Created plot.") - logger.info(f"Created plot: " f'{Path(str(file) + ".html").resolve()}') + logger.info(f"Created plot: " + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -349,8 +355,7 @@ def main(argv=None): # FI parameter sweep. device_id, sensors, alerts, owner_page, boot_log, boot_measurements, version = ( - fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication) - ) + fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication)) # Print plot. print_plot( @@ -373,12 +378,10 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) diff --git a/fault_injection/fi_gear/chipshouter/chipshouter.py b/fault_injection/fi_gear/chipshouter/chipshouter.py index 1cd0bb71..0abbea6a 100644 --- a/fault_injection/fi_gear/chipshouter/chipshouter.py +++ b/fault_injection/fi_gear/chipshouter/chipshouter.py @@ -1,6 +1,8 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 +""" EMFI with the ChipShouter and the ChipShover XZY table. +""" import sys import time @@ -22,11 +24,9 @@ sys.path.append("../") from util import check_version # noqa: E402 -""" EMFI with the ChipShouter and the ChipShover XZY table. -""" - class ChipShouterEMFI: + def __init__( self, x_position_min: float, @@ -89,7 +89,8 @@ def __init__( print("Putting XZY table to home position.") self.shv.home() print("Putting XZY table to init position.") - self.shv.move(self.x_position_min, self.y_position_min, self.z_position) + self.shv.move(self.x_position_min, self.y_position_min, + self.z_position) # Init ChipShouter. self.cs = ChipSHOUTER(chipshouter_port) @@ -131,30 +132,26 @@ def __init__( min_ns = int((1 / self.cwh.clock.clkgen_freq) * 1e9) print(min_ns) if self.pulse_width_min < min_ns: - raise RuntimeError( - "Min pulse width shorter than supported (" + str(min_ns) + " ns)" - ) + raise RuntimeError("Min pulse width shorter than supported (" + + str(min_ns) + " ns)") # Check pulse step. - if (self.pulse_width_min != self.pulse_width_max) and ( - self.pulse_width_step % min_ns != 0 - ): - raise RuntimeError( - "Only a pulse step width of " + str(min_ns) + " ns is supported" - ) + if (self.pulse_width_min + != self.pulse_width_max) and (self.pulse_width_step % min_ns + != 0): + raise RuntimeError("Only a pulse step width of " + str(min_ns) + + " ns is supported") # Check trigger delay min. if self.trigger_delay_min < min_ns: - raise RuntimeError( - "Min trigger delay shorter than supported (" + str(min_ns) + " ns)" - ) + raise RuntimeError("Min trigger delay shorter than supported (" + + str(min_ns) + " ns)") # Check trigger delay step. - if (self.trigger_delay_min != self.trigger_delay_max) and ( - self.trigger_step % min_ns != 0 - ): - raise RuntimeError( - "Only a trigger delay step of " + str(min_ns) + " ns is supported" - ) + if (self.trigger_delay_min + != self.trigger_delay_max) and (self.trigger_step % min_ns + != 0): + raise RuntimeError("Only a trigger delay step of " + str(min_ns) + + " ns is supported") def init_cs(self) -> None: time.sleep(1) @@ -190,15 +187,12 @@ def arm_trigger(self, fault_parameters: dict) -> None: self.init_cs() # Move ChipShouter to XZY position. - if ( - self.pos_y != fault_parameters["y_pos"] or - self.pos_x != fault_parameters["x_pos"] - ): + if (self.pos_y != fault_parameters["y_pos"] or + self.pos_x != fault_parameters["x_pos"]): self.pos_y = fault_parameters["y_pos"] self.pos_x = fault_parameters["x_pos"] - self.shv.move( - fault_parameters["x_pos"], fault_parameters["y_pos"], self.z_position - ) + self.shv.move(fault_parameters["x_pos"], fault_parameters["y_pos"], + self.z_position) # Set the EMFI voltage parameter. try: @@ -208,12 +202,11 @@ def arm_trigger(self, fault_parameters: dict) -> None: self.init_cs() # Configure the glitch delay and length. - self.cwh.glitch.ext_offset = int( - fault_parameters["trigger_delay"] * 1e-9 / (1 / self.cwh.clock.clkgen_freq) - ) - self.cwh.glitch.repeat = int( - fault_parameters["glitch_width"] * 1e-9 / (1 / self.cwh.clock.clkgen_freq) - ) + self.cwh.glitch.ext_offset = int(fault_parameters["trigger_delay"] * + 1e-9 / + (1 / self.cwh.clock.clkgen_freq)) + self.cwh.glitch.repeat = int(fault_parameters["glitch_width"] * 1e-9 / + (1 / self.cwh.clock.clkgen_freq)) # Arm the ChipWhisperer Husky. self.cwh.arm() @@ -229,12 +222,12 @@ def generate_fi_parameters(self) -> dict: """ parameters = {} if self.parameter_generation == "random": - parameters["x_pos"] = random_float_range( - self.x_position_min, self.x_position_max, self.x_position_step - ) - parameters["y_pos"] = random_float_range( - self.y_position_min, self.y_position_max, self.y_position_step - ) + parameters["x_pos"] = random_float_range(self.x_position_min, + self.x_position_max, + self.x_position_step) + parameters["y_pos"] = random_float_range(self.y_position_min, + self.y_position_max, + self.y_position_step) elif self.parameter_generation == "deterministic": if self.curr_iteration == self.num_iterations: self.curr_iteration = 0 @@ -255,18 +248,14 @@ def generate_fi_parameters(self) -> dict: else: raise Exception( "ChipShouter EMFI only supports random/deterministic" - "parameter generation" - ) + "parameter generation") parameters["glitch_voltage"] = random_float_range( - self.voltage_min, self.voltage_max, self.voltage_step - ) + self.voltage_min, self.voltage_max, self.voltage_step) parameters["glitch_width"] = random_float_range( - self.pulse_width_min, self.pulse_width_max, self.pulse_width_step - ) + self.pulse_width_min, self.pulse_width_max, self.pulse_width_step) parameters["trigger_delay"] = random_float_range( - self.trigger_delay_min, self.trigger_delay_max, self.trigger_step - ) + self.trigger_delay_min, self.trigger_delay_max, self.trigger_step) return parameters def reset(self) -> None: @@ -283,16 +272,11 @@ def get_num_fault_injections(self) -> int: if self.parameter_generation == "random": return self.num_iterations elif self.parameter_generation == "deterministic": - return ( - ((self.x_position_max - self.x_position_min + 1) / self.x_position_step) * - ( - (self.y_position_max - self.y_position_min + 1) / - self.y_position_step - ) * - (self.num_iterations) - ) + return (((self.x_position_max - self.x_position_min + 1) / + self.x_position_step) * + ((self.y_position_max - self.y_position_min + 1) / + self.y_position_step) * (self.num_iterations)) else: raise Exception( "ChipShouter EMFI only supports random/deterministic" - "parameter generation" - ) + "parameter generation") diff --git a/fault_injection/fi_gear/dummy/dummy_clk.py b/fault_injection/fi_gear/dummy/dummy_clk.py index 3bd5b150..42883cee 100644 --- a/fault_injection/fi_gear/dummy/dummy_clk.py +++ b/fault_injection/fi_gear/dummy/dummy_clk.py @@ -1,15 +1,15 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - -from fi_gear.utility import random_float_range - """ Template for CLK glitching gear. Acts as a template for clock glitching FI gear. """ +from fi_gear.utility import random_float_range + class DummyCLK: + def __init__( self, glitch_width_min: int, @@ -36,11 +36,9 @@ def arm_trigger(self, fault_parameters: dict) -> None: Args: A dict containing the FI parameters. """ - print( - f"Arming DummyCLK trigger with" - f"glitch_width={fault_parameters['glitch_width']}, and " - f"trigger_delay={fault_parameters['trigger_delay']}" - ) + print(f"Arming DummyCLK trigger with" + f"glitch_width={fault_parameters['glitch_width']}, and " + f"trigger_delay={fault_parameters['trigger_delay']}") def generate_fi_parameters(self) -> dict: """Generate clock glitching parameters within the provided limits. @@ -50,11 +48,10 @@ def generate_fi_parameters(self) -> dict: """ parameters = {} parameters["glitch_width"] = random_float_range( - self.glitch_width_min, self.glitch_width_max, self.glitch_width_step - ) + self.glitch_width_min, self.glitch_width_max, + self.glitch_width_step) parameters["trigger_delay"] = random_float_range( - self.trigger_delay_min, self.trigger_delay_max, self.trigger_step - ) + self.trigger_delay_min, self.trigger_delay_max, self.trigger_step) return parameters def reset(self) -> None: @@ -69,4 +66,5 @@ def get_num_fault_injections(self) -> int: if self.parameter_generation == "random": return self.num_iterations else: - raise Exception("DummyCLK only supports random parameter generation") + raise Exception( + "DummyCLK only supports random parameter generation") diff --git a/fault_injection/fi_gear/dummy/dummy_emfi.py b/fault_injection/fi_gear/dummy/dummy_emfi.py index 92fdb707..d4350fc4 100644 --- a/fault_injection/fi_gear/dummy/dummy_emfi.py +++ b/fault_injection/fi_gear/dummy/dummy_emfi.py @@ -1,15 +1,15 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - -from fi_gear.utility import random_float_range - """ Template for EMFI gear. Acts as a template for electromagnetic FI gear with XY table. """ +from fi_gear.utility import random_float_range + class DummyEMFI: + def __init__( self, x_position_min: int, @@ -61,14 +61,12 @@ def arm_trigger(self, fault_parameters: dict) -> None: Args: A dict containing the FI parameters. """ - print( - f"Arming DummyEMFI trigger with " - f"x_position={fault_parameters['x_position']} " - f"y_position={fault_parameters['y_position']}, " - f"voltage={fault_parameters['voltage']}, " - f"pulse_width={fault_parameters['pulse_width']}, and " - f"trigger_delay={fault_parameters['trigger_delay']}" - ) + print(f"Arming DummyEMFI trigger with " + f"x_position={fault_parameters['x_position']} " + f"y_position={fault_parameters['y_position']}, " + f"voltage={fault_parameters['voltage']}, " + f"pulse_width={fault_parameters['pulse_width']}, and " + f"trigger_delay={fault_parameters['trigger_delay']}") def generate_fi_parameters(self) -> dict: """Generate EMFI parameters within the provided limits. @@ -79,11 +77,9 @@ def generate_fi_parameters(self) -> dict: parameters = {} if self.parameter_generation == "random": parameters["x_position"] = random_float_range( - self.x_position_min, self.x_position_max, self.x_position_step - ) + self.x_position_min, self.x_position_max, self.x_position_step) parameters["y_position"] = random_float_range( - self.y_position_min, self.y_position_max, self.y_position_step - ) + self.y_position_min, self.y_position_max, self.y_position_step) elif self.parameter_generation == "deterministic": if self.curr_iteration == self.num_iterations: self.curr_iteration = 0 @@ -101,15 +97,14 @@ def generate_fi_parameters(self) -> dict: "DummyEMFI only supports random/deterministic parameter generation" ) - parameters["voltage"] = random_float_range( - self.voltage_min, self.voltage_max, self.voltage_step - ) - parameters["pulse_width"] = random_float_range( - self.pulse_width_min, self.pulse_width_max, self.pulse_width_step - ) + parameters["voltage"] = random_float_range(self.voltage_min, + self.voltage_max, + self.voltage_step) + parameters["pulse_width"] = random_float_range(self.pulse_width_min, + self.pulse_width_max, + self.pulse_width_step) parameters["trigger_delay"] = random_float_range( - self.trigger_delay_min, self.trigger_delay_max, self.trigger_step - ) + self.trigger_delay_min, self.trigger_delay_max, self.trigger_step) return parameters def reset(self) -> None: @@ -124,14 +119,10 @@ def get_num_fault_injections(self) -> int: if self.parameter_generation == "random": return self.num_iterations elif self.parameter_generation == "deterministic": - return ( - ((self.x_position_max - self.x_position_min + 1) / self.x_position_step) * - ( - (self.y_position_max - self.y_position_min + 1) / - self.y_position_step - ) * - (self.num_iterations) - ) + return (((self.x_position_max - self.x_position_min + 1) / + self.x_position_step) * + ((self.y_position_max - self.y_position_min + 1) / + self.y_position_step) * (self.num_iterations)) else: raise Exception( "DummyEMFI only supports random/deterministic parameter generation" diff --git a/fault_injection/fi_gear/dummy/dummy_lfi.py b/fault_injection/fi_gear/dummy/dummy_lfi.py index 514bc169..836b510b 100644 --- a/fault_injection/fi_gear/dummy/dummy_lfi.py +++ b/fault_injection/fi_gear/dummy/dummy_lfi.py @@ -1,15 +1,15 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - -from fi_gear.utility import random_float_range - """ Template for LFI gear. Acts as a template for laser FI gear with XY table. """ +from fi_gear.utility import random_float_range + class DummyLFI: + def __init__( self, x_position_min: int, @@ -61,14 +61,12 @@ def arm_trigger(self, fault_parameters: dict) -> None: Args: A dict containing the FI parameters. """ - print( - f"Arming DummyLFI trigger with" - f"x_position={fault_parameters['x_position']} " - f"y_position={fault_parameters['y_position']}, " - f"attenuation={fault_parameters['attenuation']}, " - f"pulse_width={fault_parameters['pulse_width']}, and " - f"trigger_delay={fault_parameters['trigger_delay']}" - ) + print(f"Arming DummyLFI trigger with" + f"x_position={fault_parameters['x_position']} " + f"y_position={fault_parameters['y_position']}, " + f"attenuation={fault_parameters['attenuation']}, " + f"pulse_width={fault_parameters['pulse_width']}, and " + f"trigger_delay={fault_parameters['trigger_delay']}") def generate_fi_parameters(self) -> dict: """Generate LFI parameters within the provided limits. @@ -79,11 +77,9 @@ def generate_fi_parameters(self) -> dict: parameters = {} if self.parameter_generation == "random": parameters["x_position"] = random_float_range( - self.x_position_min, self.x_position_max, self.x_position_step - ) + self.x_position_min, self.x_position_max, self.x_position_step) parameters["y_position"] = random_float_range( - self.y_position_min, self.y_position_max, self.y_position_step - ) + self.y_position_min, self.y_position_max, self.y_position_step) elif self.parameter_generation == "deterministic": if self.curr_iteration == self.num_iterations: self.curr_iteration = 0 @@ -101,15 +97,14 @@ def generate_fi_parameters(self) -> dict: "DummyLFI only supports random/deterministic parameter generation" ) - parameters["attenuation"] = random_float_range( - self.attenuation_min, self.attenuation_max, self.attenuation_step - ) - parameters["pulse_width"] = random_float_range( - self.pulse_width_min, self.pulse_width_max, self.pulse_width_step - ) + parameters["attenuation"] = random_float_range(self.attenuation_min, + self.attenuation_max, + self.attenuation_step) + parameters["pulse_width"] = random_float_range(self.pulse_width_min, + self.pulse_width_max, + self.pulse_width_step) parameters["trigger_delay"] = random_float_range( - self.trigger_delay_min, self.trigger_delay_max, self.trigger_step - ) + self.trigger_delay_min, self.trigger_delay_max, self.trigger_step) return parameters def reset(self) -> None: @@ -124,14 +119,10 @@ def get_num_fault_injections(self) -> int: if self.parameter_generation == "random": return self.num_iterations elif self.parameter_generation == "deterministic": - return ( - ((self.x_position_max - self.x_position_min + 1) / self.x_position_step) * - ( - (self.y_position_max - self.y_position_min + 1) / - self.y_position_step - ) * - (self.num_iterations) - ) + return (((self.x_position_max - self.x_position_min + 1) / + self.x_position_step) * + ((self.y_position_max - self.y_position_min + 1) / + self.y_position_step) * (self.num_iterations)) else: raise Exception( "DummyEMFI only supports random/deterministic parameter generation" diff --git a/fault_injection/fi_gear/dummy/dummy_vcc.py b/fault_injection/fi_gear/dummy/dummy_vcc.py index e9e98007..13fc76a2 100644 --- a/fault_injection/fi_gear/dummy/dummy_vcc.py +++ b/fault_injection/fi_gear/dummy/dummy_vcc.py @@ -1,15 +1,15 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - -from fi_gear.utility import random_float_range - """ Template for VCC glitching gear. Acts as a template for voltage glitching FI gear. """ +from fi_gear.utility import random_float_range + class DummyVCC: + def __init__( self, glitch_voltage_min: float, @@ -42,12 +42,10 @@ def arm_trigger(self, fault_parameters: dict) -> None: Args: A dict containing the FI parameters. """ - print( - f"Arming DummyVCC trigger with " - f"glitch_voltage={fault_parameters['glitch_voltage']}, " - f"glitch_width={fault_parameters['glitch_width']}, and " - f"trigger_delay={fault_parameters['trigger_delay']}" - ) + print(f"Arming DummyVCC trigger with " + f"glitch_voltage={fault_parameters['glitch_voltage']}, " + f"glitch_width={fault_parameters['glitch_width']}, and " + f"trigger_delay={fault_parameters['trigger_delay']}") def generate_fi_parameters(self) -> dict: """Generate random voltage glitch parameters within the provided @@ -64,13 +62,14 @@ def generate_fi_parameters(self) -> dict: self.glitch_voltage_step, ) parameters["glitch_width"] = random_float_range( - self.glitch_width_min, self.glitch_width_max, self.glitch_width_step - ) + self.glitch_width_min, self.glitch_width_max, + self.glitch_width_step) parameters["trigger_delay"] = random_float_range( - self.trigger_delay_min, self.trigger_delay_max, self.trigger_step - ) + self.trigger_delay_min, self.trigger_delay_max, + self.trigger_step) else: - raise Exception("DummyVCC only supports random parameter generation") + raise Exception( + "DummyVCC only supports random parameter generation") return parameters @@ -87,4 +86,5 @@ def get_num_fault_injections(self) -> int: if self.parameter_generation == "random": return self.num_iterations else: - raise Exception("DummyVCC only supports random parameter generation") + raise Exception( + "DummyVCC only supports random parameter generation") diff --git a/fault_injection/fi_gear/husky/husky_vcc.py b/fault_injection/fi_gear/husky/husky_vcc.py index cb594220..88786b14 100644 --- a/fault_injection/fi_gear/husky/husky_vcc.py +++ b/fault_injection/fi_gear/husky/husky_vcc.py @@ -116,8 +116,8 @@ def generate_fi_parameters(self) -> dict: parameters = {} if self.parameter_generation == "random": parameters["glitch_width"] = random_float_range( - self.glitch_width_min, self.glitch_width_max, self.glitch_width_step - ) + self.glitch_width_min, self.glitch_width_max, + self.glitch_width_step) elif self.parameter_generation == "deterministic": if self.curr_iteration == self.num_iterations: self.curr_iteration = 0 @@ -131,8 +131,7 @@ def generate_fi_parameters(self) -> dict: # Randomly generate the trigger delay for both cases. parameters["trigger_delay"] = random_float_range( - self.trigger_delay_min, self.trigger_delay_max, self.trigger_step - ) + self.trigger_delay_min, self.trigger_delay_max, self.trigger_step) return parameters def reset(self) -> None: @@ -151,9 +150,8 @@ def get_num_fault_injections(self) -> int: if self.parameter_generation == "random": return self.num_iterations elif self.parameter_generation == "deterministic": - return ( - (self.glitch_width_max - self.glitch_width_min) / self.glitch_width_step - ) * self.num_iterations + return ((self.glitch_width_max - self.glitch_width_min) / + self.glitch_width_step) * self.num_iterations else: raise Exception( "HuskyVCC only supports random/deterministic parameter generation" diff --git a/fault_injection/fi_gear/utility.py b/fault_injection/fi_gear/utility.py index 29648f5e..3cc591e1 100644 --- a/fault_injection/fi_gear/utility.py +++ b/fault_injection/fi_gear/utility.py @@ -7,4 +7,5 @@ def random_float_range(min: float, max: float, step: float) -> float: """Returns a random float between min and max with step.""" - return round(random.randint(0, int(round((max - min) / step))) * step + min, 4) + return round( + random.randint(0, int(round((max - min) / step))) * step + min, 4) diff --git a/fault_injection/fi_ibex.py b/fault_injection/fi_ibex.py index 8862e92b..2b00c633 100755 --- a/fault_injection/fi_ibex.py +++ b/fault_injection/fi_ibex.py @@ -34,9 +34,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Create target config & setup target. logger.info(f"Initializing target {cfg['target']['target_type']} ...") @@ -50,7 +49,7 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["fisetup"].get("usb_serial"), + husky_serial=cfg["fisetup"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) @@ -81,20 +80,21 @@ def print_fi_statistic(fi_results: list) -> None: fi_results: The FI results. """ num_total = len(fi_results) - num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, 2) - num_exp = round((fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) - num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, 2) + num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, + 2) + num_exp = round( + (fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) + num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, + 2) logger.info( f"{num_total} faults, {fi_results.count(FISuccess.SUCCESS)}" f"({num_succ}%) successful, {fi_results.count(FISuccess.EXPRESPONSE)}" f"({num_exp}%) expected, and {fi_results.count(FISuccess.NORESPONSE)}" - f"({num_no}%) no response." - ) + f"({num_no}%) no response.") -def fi_parameter_sweep( - cfg: dict, target: Target, fi_gear, project: FIProject, ot_communication: OTFIIbex -) -> None: +def fi_parameter_sweep(cfg: dict, target: Target, fi_gear, project: FIProject, + ot_communication: OTFIIbex) -> None: """Fault parameter sweep. Sweep through the fault parameter space. @@ -120,15 +120,15 @@ def fi_parameter_sweep( cfg["test"]["core_config"], cfg["test"]["sensor_config"], cfg["test"]["alert_config"], - ) - ) + )) # Store results in array for a quick access. fi_results = [] # Start the parameter sweep. remaining_iterations = fi_gear.get_num_fault_injections() - with tqdm( - total=remaining_iterations, desc="Injecting", ncols=80, unit=" different faults" - ) as pbar: + with tqdm(total=remaining_iterations, + desc="Injecting", + ncols=80, + unit=" different faults") as pbar: while remaining_iterations > 0: # Get fault parameters (e.g., trigger delay, glitch voltage). fault_parameters = fi_gear.generate_fi_parameters() @@ -168,7 +168,8 @@ def fi_parameter_sweep( resp_json = json.loads(response_compare) if "registers" in resp_json: del resp_json["registers"] - response_compare = json.dumps(resp_json, separators=(",", ":")) + response_compare = json.dumps(resp_json, + separators=(",", ":")) # If the test decides to ignore alerts triggered by the alert # handler, remove it from the received and expected response. # In the database, the received alert is still available for @@ -178,10 +179,12 @@ def fi_parameter_sweep( exp_json = json.loads(expected_response) if "alerts" in resp_json: del resp_json["alerts"] - response_compare = json.dumps(resp_json, separators=(",", ":")) + response_compare = json.dumps(resp_json, + separators=(",", ":")) if "alerts" in exp_json: del exp_json["alerts"] - expected_response = json.dumps(exp_json, separators=(",", ":")) + expected_response = json.dumps(exp_json, + separators=(",", ":")) # Check if result is expected result (FI failed), unexpected result # (FI successful), or no response (FI failed.) @@ -221,7 +224,8 @@ def print_plot(project: FIProject, config: dict, file: Path) -> None: if config["fiproject"]["show_plot"]: plot.save_fi_plot_to_file(config, project, file) logger.info("Created plot.") - logger.info(f"Created plot: " f'{Path(str(file) + ".html").resolve()}') + logger.info(f"Created plot: " + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -245,8 +249,7 @@ def main(argv=None): # FI parameter sweep. device_id, sensors, alerts, owner_page, boot_log, boot_measurements, version = ( - fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication) - ) + fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication)) # Print plot. print_plot( @@ -269,12 +272,10 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) diff --git a/fault_injection/fi_otbn.py b/fault_injection/fi_otbn.py index b386a119..b6e6ba4d 100755 --- a/fault_injection/fi_otbn.py +++ b/fault_injection/fi_otbn.py @@ -34,9 +34,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Create target config & setup target. logger.info(f"Initializing target {cfg['target']['target_type']} ...") @@ -50,7 +49,7 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["fisetup"].get("usb_serial"), + husky_serial=cfg["fisetup"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) @@ -81,20 +80,21 @@ def print_fi_statistic(fi_results: list) -> None: fi_results: The FI results. """ num_total = len(fi_results) - num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, 2) - num_exp = round((fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) - num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, 2) + num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, + 2) + num_exp = round( + (fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) + num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, + 2) logger.info( f"{num_total} faults, {fi_results.count(FISuccess.SUCCESS)}" f"({num_succ}%) successful, {fi_results.count(FISuccess.EXPRESPONSE)}" f"({num_exp}%) expected, and {fi_results.count(FISuccess.NORESPONSE)}" - f"({num_no}%) no response." - ) + f"({num_no}%) no response.") -def fi_parameter_sweep( - cfg: dict, target: Target, fi_gear, project: FIProject, ot_communication: OTFIOtbn -) -> None: +def fi_parameter_sweep(cfg: dict, target: Target, fi_gear, project: FIProject, + ot_communication: OTFIOtbn) -> None: """Fault parameter sweep. Sweep through the fault parameter space. @@ -120,8 +120,7 @@ def fi_parameter_sweep( cfg["test"]["core_config"], cfg["test"]["sensor_config"], cfg["test"]["alert_config"], - ) - ) + )) # Setup key manager if needed by test. ot_communication.init_keymgr(cfg["test"]["which_test"]) # Store results in array for a quick access. @@ -131,9 +130,10 @@ def fi_parameter_sweep( # Helper variable tracking whether we already transmitted the test config # if one exists. config_transmitted = False - with tqdm( - total=remaining_iterations, desc="Injecting", ncols=80, unit=" different faults" - ) as pbar: + with tqdm(total=remaining_iterations, + desc="Injecting", + ncols=80, + unit=" different faults") as pbar: while remaining_iterations > 0: # Get fault parameters (e.g., trigger delay, glitch voltage). fault_parameters = fi_gear.generate_fi_parameters() @@ -184,7 +184,8 @@ def fi_parameter_sweep( resp_json = json.loads(response_compare) if "data" in resp_json: del resp_json["data"] - response_compare = json.dumps(resp_json, separators=(",", ":")) + response_compare = json.dumps(resp_json, + separators=(",", ":")) # Most but not all tests (e.g., tests returning random data) # expect a certain response. expected_response = cfg["test"]["expected_result"] @@ -197,10 +198,12 @@ def fi_parameter_sweep( exp_json = json.loads(expected_response) if "alerts" in resp_json: del resp_json["alerts"] - response_compare = json.dumps(resp_json, separators=(",", ":")) + response_compare = json.dumps(resp_json, + separators=(",", ":")) if "alerts" in exp_json: del exp_json["alerts"] - expected_response = json.dumps(exp_json, separators=(",", ":")) + expected_response = json.dumps(exp_json, + separators=(",", ":")) # Check if result is expected result (FI failed), unexpected result # (FI successful), or no response (FI failed.) @@ -240,7 +243,8 @@ def print_plot(project: FIProject, config: dict, file: Path) -> None: if config["fiproject"]["show_plot"]: plot.save_fi_plot_to_file(config, project, file) logger.info("Created plot.") - logger.info(f"Created plot: " f'{Path(str(file) + ".html").resolve()}') + logger.info(f"Created plot: " + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -264,8 +268,7 @@ def main(argv=None): # FI parameter sweep. device_id, sensors, alerts, owner_page, boot_log, boot_measurements, version = ( - fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication) - ) + fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication)) # Print plot. print_plot( @@ -288,12 +291,10 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) diff --git a/fault_injection/fi_otp.py b/fault_injection/fi_otp.py index 0e375dda..e232cf73 100755 --- a/fault_injection/fi_otp.py +++ b/fault_injection/fi_otp.py @@ -34,9 +34,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Create target config & setup target. logger.info(f"Initializing target {cfg['target']['target_type']} ...") @@ -79,20 +78,21 @@ def print_fi_statistic(fi_results: list) -> None: fi_results: The FI results. """ num_total = len(fi_results) - num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, 2) - num_exp = round((fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) - num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, 2) + num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, + 2) + num_exp = round( + (fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) + num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, + 2) logger.info( f"{num_total} faults, {fi_results.count(FISuccess.SUCCESS)}" f"({num_succ}%) successful, {fi_results.count(FISuccess.EXPRESPONSE)}" f"({num_exp}%) expected, and {fi_results.count(FISuccess.NORESPONSE)}" - f"({num_no}%) no response." - ) + f"({num_no}%) no response.") -def fi_parameter_sweep( - cfg: dict, target: Target, fi_gear, project: FIProject, ot_communication: OTFIOtp -) -> None: +def fi_parameter_sweep(cfg: dict, target: Target, fi_gear, project: FIProject, + ot_communication: OTFIOtp) -> None: """Fault parameter sweep. Sweep through the fault parameter space. @@ -118,15 +118,15 @@ def fi_parameter_sweep( cfg["test"]["core_config"], cfg["test"]["sensor_config"], cfg["test"]["alert_config"], - ) - ) + )) # Store results in array for a quick access. fi_results = [] # Start the parameter sweep. remaining_iterations = fi_gear.get_num_fault_injections() - with tqdm( - total=remaining_iterations, desc="Injecting", ncols=80, unit=" different faults" - ) as pbar: + with tqdm(total=remaining_iterations, + desc="Injecting", + ncols=80, + unit=" different faults") as pbar: while remaining_iterations > 0: # Get fault parameters (e.g., trigger delay, glitch voltage). fault_parameters = fi_gear.generate_fi_parameters() @@ -168,10 +168,12 @@ def fi_parameter_sweep( exp_json = json.loads(expected_response) if "alerts" in resp_json: del resp_json["alerts"] - response_compare = json.dumps(resp_json, separators=(",", ":")) + response_compare = json.dumps(resp_json, + separators=(",", ":")) if "alerts" in exp_json: del exp_json["alerts"] - expected_response = json.dumps(exp_json, separators=(",", ":")) + expected_response = json.dumps(exp_json, + separators=(",", ":")) resp_json = json.loads(response_compare) exp_json = json.loads(expected_response) @@ -215,7 +217,8 @@ def print_plot(project: FIProject, config: dict, file: Path) -> None: if config["fiproject"]["show_plot"]: plot.save_fi_plot_to_file(config, project, file) logger.info("Created plot.") - logger.info(f"Created plot: " f'{Path(str(file) + ".html").resolve()}') + logger.info(f"Created plot: " + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -239,8 +242,7 @@ def main(argv=None): # FI parameter sweep. device_id, sensors, alerts, owner_page, boot_log, boot_measurements, version = ( - fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication) - ) + fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication)) # Print plot. print_plot( @@ -263,12 +265,10 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) diff --git a/fault_injection/fi_rng.py b/fault_injection/fi_rng.py index d437185c..bb2fcbb0 100755 --- a/fault_injection/fi_rng.py +++ b/fault_injection/fi_rng.py @@ -34,9 +34,8 @@ def setup(cfg: dict, project: Path): # Calculate pll_frequency of the target. # target_freq = pll_frequency * target_clk_mult # target_clk_mult is a hardcoded constant in the FPGA bitstream. - cfg["target"]["pll_frequency"] = ( - cfg["target"]["target_freq"] / cfg["target"]["target_clk_mult"] - ) + cfg["target"]["pll_frequency"] = (cfg["target"]["target_freq"] / + cfg["target"]["target_clk_mult"]) # Create target config & setup target. logger.info(f"Initializing target {cfg['target']['target_type']} ...") @@ -50,7 +49,7 @@ def setup(cfg: dict, project: Path): port=cfg["target"].get("port"), usb_serial=cfg["target"].get("usb_serial"), interface=cfg["target"].get("interface"), - husky_serial = cfg["fisetup"].get("usb_serial"), + husky_serial=cfg["fisetup"].get("usb_serial"), opentitantool=cfg["target"]["opentitantool"], ) target = Target(target_cfg) @@ -81,20 +80,21 @@ def print_fi_statistic(fi_results: list) -> None: fi_results: The FI results. """ num_total = len(fi_results) - num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, 2) - num_exp = round((fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) - num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, 2) + num_succ = round((fi_results.count(FISuccess.SUCCESS) / num_total) * 100, + 2) + num_exp = round( + (fi_results.count(FISuccess.EXPRESPONSE) / num_total) * 100, 2) + num_no = round((fi_results.count(FISuccess.NORESPONSE) / num_total) * 100, + 2) logger.info( f"{num_total} faults, {fi_results.count(FISuccess.SUCCESS)}" f"({num_succ}%) successful, {fi_results.count(FISuccess.EXPRESPONSE)}" f"({num_exp}%) expected, and {fi_results.count(FISuccess.NORESPONSE)}" - f"({num_no}%) no response." - ) + f"({num_no}%) no response.") -def fi_parameter_sweep( - cfg: dict, target: Target, fi_gear, project: FIProject, ot_communication: OTFIRng -) -> None: +def fi_parameter_sweep(cfg: dict, target: Target, fi_gear, project: FIProject, + ot_communication: OTFIRng) -> None: """Fault parameter sweep. Sweep through the fault parameter space. @@ -121,15 +121,15 @@ def fi_parameter_sweep( cfg["test"]["core_config"], cfg["test"]["sensor_config"], cfg["test"]["alert_config"], - ) - ) + )) # Store results in array for a quick access. fi_results = [] # Start the parameter sweep. remaining_iterations = fi_gear.get_num_fault_injections() - with tqdm( - total=remaining_iterations, desc="Injecting", ncols=80, unit=" different faults" - ) as pbar: + with tqdm(total=remaining_iterations, + desc="Injecting", + ncols=80, + unit=" different faults") as pbar: while remaining_iterations > 0: # Get fault parameters (e.g., trigger delay, glitch voltage). fault_parameters = fi_gear.generate_fi_parameters() @@ -178,10 +178,12 @@ def fi_parameter_sweep( exp_json = json.loads(expected_response) if "alerts" in resp_json: del resp_json["alerts"] - response_compare = json.dumps(resp_json, separators=(",", ":")) + response_compare = json.dumps(resp_json, + separators=(",", ":")) if "alerts" in exp_json: del exp_json["alerts"] - expected_response = json.dumps(exp_json, separators=(",", ":")) + expected_response = json.dumps(exp_json, + separators=(",", ":")) # Check if result is expected result (FI failed) or unexpected # result (FI successful). @@ -221,7 +223,8 @@ def print_plot(project: FIProject, config: dict, file: Path) -> None: if config["fiproject"]["show_plot"]: plot.save_fi_plot_to_file(config, project, file) logger.info("Created plot.") - logger.info(f"Created plot: " f'{Path(str(file) + ".html").resolve()}') + logger.info(f"Created plot: " + f'{Path(str(file) + ".html").resolve()}') def main(argv=None): @@ -245,8 +248,7 @@ def main(argv=None): # FI parameter sweep. device_id, sensors, alerts, owner_page, boot_log, boot_measurements, version = ( - fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication) - ) + fi_parameter_sweep(cfg, target, fi_gear, project, ot_communication)) # Print plot. print_plot( @@ -269,12 +271,10 @@ def main(argv=None): metadata["fpga_bitstream_path"] = cfg["target"].get("fpga_bitstream") if cfg["target"].get("fpga_bitstream") is not None: metadata["fpga_bitstream_crc"] = helpers.file_crc( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) if args.save_bitstream: metadata["fpga_bitstream"] = helpers.get_binary_blob( - cfg["target"]["fpga_bitstream"] - ) + cfg["target"]["fpga_bitstream"]) # Store binary information. metadata["fw_bin_path"] = cfg["target"]["fw_bin"] metadata["fw_bin_crc"] = helpers.file_crc(cfg["target"]["fw_bin"]) diff --git a/fault_injection/project_library/ot_fi_library/fi_library.py b/fault_injection/project_library/ot_fi_library/fi_library.py index b957e2b3..bc737705 100644 --- a/fault_injection/project_library/ot_fi_library/fi_library.py +++ b/fault_injection/project_library/ot_fi_library/fi_library.py @@ -46,7 +46,8 @@ class FILibrary: one. FIResults are first written into memory and then flushed into the database after reaching a FIResults memory threshold. """ - def __init__(self, db_name, fi_threshold, overwrite = False): + + def __init__(self, db_name, fi_threshold, overwrite=False): if overwrite: fi_lib_file = Path(db_name + ".db") fi_lib_file.unlink(missing_ok=True) @@ -57,7 +58,9 @@ def __init__(self, db_name, fi_threshold, overwrite = False): self.firesults_table = db.Table( "firesults", self.metadata, - db.Column("fi_id", db.Integer, primary_key=True, + db.Column("fi_id", + db.Integer, + primary_key=True, autoincrement=True), db.Column("response", db.String), db.Column("fi_result", db.Integer), @@ -67,11 +70,8 @@ def __init__(self, db_name, fi_threshold, overwrite = False): db.Column("x_pos", db.Integer), db.Column("y_pos", db.Integer), ) - self.metadata_table = db.Table( - "metadata", - self.metadata, - db.Column("data", db.PickleType) - ) + self.metadata_table = db.Table("metadata", self.metadata, + db.Column("data", db.PickleType)) self.metadata.create_all(self.engine) self.fi_mem = [] self.fi_mem_thr = fi_threshold @@ -104,7 +104,8 @@ def write_to_buffer(self, fi_result): if len(self.fi_mem) >= self.fi_mem_thr: self.flush_to_disk() - def get_firesults(self, start: Optional[int] = None, + def get_firesults(self, + start: Optional[int] = None, end: Optional[int] = None): """ Get FIResults from database and store into RAM. @@ -132,8 +133,10 @@ def get_firesults(self, start: Optional[int] = None, else: query = db.select(self.firesults_table) - return [FIResult(**firesult._mapping) - for firesult in self.session.execute(query).fetchall()] + return [ + FIResult(**firesult._mapping) + for firesult in self.session.execute(query).fetchall() + ] def write_metadata(self, metadata): """ Write metadata into database. @@ -153,5 +156,6 @@ def get_metadata(self): The metadata from the database. """ query = db.select(self.metadata_table) - metadata = Metadata(**self.session.execute(query).fetchall()[0]._mapping) + metadata = Metadata( + **self.session.execute(query).fetchall()[0]._mapping) return pickle.loads(bytes(metadata.data, encoding="latin1")) diff --git a/fault_injection/project_library/project.py b/fault_injection/project_library/project.py index aff2994c..dfb3bd52 100644 --- a/fault_injection/project_library/project.py +++ b/fault_injection/project_library/project.py @@ -97,7 +97,9 @@ def append_firesult( ) self.project.write_to_buffer(firesult) - def get_firesults(self, start: Optional[int] = None, end: Optional[int] = None): + def get_firesults(self, + start: Optional[int] = None, + end: Optional[int] = None): """Get FI results from database and stored into RAM. Fetch FI results from start to end from database storage into RAM. diff --git a/python-requirements-lint.txt b/python-requirements-lint.txt index 8f45542c..066a43ed 100644 --- a/python-requirements-lint.txt +++ b/python-requirements-lint.txt @@ -3,6 +3,6 @@ # SPDX-License-Identifier: Apache-2.0 # Linters -flake8 -isort -yapf +flake8==7.2.0 +isort==5.13.2 +yapf==0.43.0 diff --git a/target/chip.py b/target/chip.py index 975ab62e..8829a66c 100644 --- a/target/chip.py +++ b/target/chip.py @@ -12,7 +12,9 @@ class Chip: firmware & provides helper functions. """ - def __init__(self, opentitantool_path, interface: Optional[str] = "hyper310"): + def __init__(self, + opentitantool_path, + interface: Optional[str] = "hyper310"): self.opentitantool = opentitantool_path self.interface = interface @@ -21,18 +23,16 @@ def __init__(self, opentitantool_path, interface: Optional[str] = "hyper310"): # "/path/to/opentitan/bazel-bin/sw/host/opentitantool/opentitantool" # Firmware is the pre-compiled and signed binary def flash_target(self, firmware, boot_delay=4): - flash_process = Popen( - [ - self.opentitantool, - "--rcfile=", - "--interface=" + self.interface, - "--exec", - "transport init", - "--exec", - "bootstrap " + firmware, - "no-op", - ] - ) + flash_process = Popen([ + self.opentitantool, + "--rcfile=", + "--interface=" + self.interface, + "--exec", + "transport init", + "--exec", + "bootstrap " + firmware, + "no-op", + ]) flash_process.communicate() rc = flash_process.returncode if rc != 0: @@ -48,18 +48,16 @@ def flash_target(self, firmware, boot_delay=4): # Firmware is the pre-compiled and signed binary # This uses the rescue protocol in order to flash the binary def flash_rescue_target(self, firmware, boot_delay=50): - flash_process = Popen( - [ - self.opentitantool, - "--rcfile=", - "--interface=" + self.interface, - "--exec", - "transport init", - "--exec", - "rescue firmware " + firmware, - "no-op", - ] - ) + flash_process = Popen([ + self.opentitantool, + "--rcfile=", + "--interface=" + self.interface, + "--exec", + "transport init", + "--exec", + "rescue firmware " + firmware, + "no-op", + ]) flash_process.communicate() rc = flash_process.returncode if rc != 0: @@ -74,20 +72,18 @@ def flash_rescue_target(self, firmware, boot_delay=50): # "/path/to/opentitan/bazel-bin/sw/host/opentitantool/opentitantool" def reset_target(self, reset_delay=0.005): """Reset OpenTitan by triggering the reset pin using opentitantool.""" - reset_process = Popen( - [ - self.opentitantool, - "--rcfile=", - "--interface=" + self.interface, - "--exec", - "transport init", - "--exec", - "gpio write RESET false", - "--exec", - "gpio write RESET true", - "no-op", - ] - ) + reset_process = Popen([ + self.opentitantool, + "--rcfile=", + "--interface=" + self.interface, + "--exec", + "transport init", + "--exec", + "gpio write RESET false", + "--exec", + "gpio write RESET true", + "no-op", + ]) reset_process.communicate() rc = reset_process.returncode if rc != 0: diff --git a/target/communication/common_library.py b/target/communication/common_library.py index bfb48a09..27aa6759 100644 --- a/target/communication/common_library.py +++ b/target/communication/common_library.py @@ -11,7 +11,8 @@ "enable_data_ind_timing": True, } default_sensor_config = { - "sensor_ctrl_enable": True, + "sensor_ctrl_enable": + True, "sensor_ctrl_en_fatal": [ False, False, @@ -165,7 +166,8 @@ "accumulation_thresholds": [2, 2, 2, 2], "signals": [4294967295, 0, 2, 3], "duration_cycles": [0, 7200, 48, 48], - "ping_timeout": 1200, + "ping_timeout": + 1200, } default_fpga_friendly_alert_config = { @@ -307,5 +309,6 @@ "accumulation_thresholds": [2, 2, 2, 2], "signals": [4294967295, 0, 2, 3], "duration_cycles": [0, 7200, 48, 48], - "ping_timeout": 1200, + "ping_timeout": + 1200, } diff --git a/target/communication/fi_alert_commands.py b/target/communication/fi_alert_commands.py index 6471612b..14b26dac 100644 --- a/target/communication/fi_alert_commands.py +++ b/target/communication/fi_alert_commands.py @@ -12,6 +12,7 @@ class OTFIAlert: + def __init__(self, target) -> None: self.target = target diff --git a/target/communication/fi_crypto_commands.py b/target/communication/fi_crypto_commands.py index b5cc163c..a6478a1f 100644 --- a/target/communication/fi_crypto_commands.py +++ b/target/communication/fi_crypto_commands.py @@ -12,6 +12,7 @@ class OTFICrypto: + def __init__(self, target) -> None: self.target = target diff --git a/target/communication/fi_ibex_commands.py b/target/communication/fi_ibex_commands.py index 47d4102e..5f295971 100644 --- a/target/communication/fi_ibex_commands.py +++ b/target/communication/fi_ibex_commands.py @@ -12,6 +12,7 @@ class OTFIIbex: + def __init__(self, target) -> None: self.target = target @@ -49,7 +50,8 @@ def ibex_char_unrolled_reg_op_loop_chain(self) -> None: self._ujson_ibex_fi_cmd() # CharUnrolledRegOpLoopChain command. time.sleep(0.01) - self.target.write(json.dumps("CharUnrolledRegOpLoopChain").encode("ascii")) + self.target.write( + json.dumps("CharUnrolledRegOpLoopChain").encode("ascii")) def ibex_char_mem_op_loop(self) -> None: """Starts the ibex.char.mem_op_loop test.""" @@ -101,7 +103,8 @@ def ibex_char_sram_write_static_unrolled(self) -> None: self._ujson_ibex_fi_cmd() # CharSramWriteStaticUnrolled command. time.sleep(0.01) - self.target.write(json.dumps("CharSramWriteStaticUnrolled").encode("ascii")) + self.target.write( + json.dumps("CharSramWriteStaticUnrolled").encode("ascii")) def ibex_char_sram_write_read(self) -> None: """Starts the ibex.char.sram_write_read test.""" @@ -262,8 +265,7 @@ def ibex_char_hardened_check_eq_complement_branch(self) -> None: # CharHardenedCheckComplementBranch command. time.sleep(0.01) self.target.write( - json.dumps("CharHardenedCheckComplementBranch").encode("ascii") - ) + json.dumps("CharHardenedCheckComplementBranch").encode("ascii")) def ibex_char_register_file(self) -> None: """Starts the ibex.char.register_file test.""" @@ -400,7 +402,8 @@ def ibex_char_hardened_check_eq_2_unimps(self) -> None: self._ujson_ibex_fi_cmd() # CharHardenedCheck2Unimps command. time.sleep(0.01) - self.target.write(json.dumps("CharHardenedCheck2Unimps").encode("ascii")) + self.target.write( + json.dumps("CharHardenedCheck2Unimps").encode("ascii")) def ibex_char_hardened_check_eq_3_unimps(self) -> None: """Starts the ibex.fi.char.hardened_check_eq_3_unimps test.""" @@ -408,7 +411,8 @@ def ibex_char_hardened_check_eq_3_unimps(self) -> None: self._ujson_ibex_fi_cmd() # CharHardenedCheck3Unimps command. time.sleep(0.01) - self.target.write(json.dumps("CharHardenedCheck3Unimps").encode("ascii")) + self.target.write( + json.dumps("CharHardenedCheck3Unimps").encode("ascii")) def ibex_char_hardened_check_eq_4_unimps(self) -> None: """Starts the ibex.fi.char.hardened_check_eq_4_unimps test.""" @@ -416,7 +420,8 @@ def ibex_char_hardened_check_eq_4_unimps(self) -> None: self._ujson_ibex_fi_cmd() # CharHardenedCheck4Unimps command. time.sleep(0.01) - self.target.write(json.dumps("CharHardenedCheck4Unimps").encode("ascii")) + self.target.write( + json.dumps("CharHardenedCheck4Unimps").encode("ascii")) def ibex_char_hardened_check_eq_5_unimps(self) -> None: """Starts the ibex.fi.char.hardened_check_eq_5_unimps test.""" @@ -424,7 +429,8 @@ def ibex_char_hardened_check_eq_5_unimps(self) -> None: self._ujson_ibex_fi_cmd() # CharHardenedCheck5Unimps command. time.sleep(0.01) - self.target.write(json.dumps("CharHardenedCheck5Unimps").encode("ascii")) + self.target.write( + json.dumps("CharHardenedCheck5Unimps").encode("ascii")) def ibex_char_combi(self) -> None: """Starts the ibex.fi.char.combi test.""" diff --git a/target/communication/fi_otbn_commands.py b/target/communication/fi_otbn_commands.py index 3abd4311..a6a95fbc 100644 --- a/target/communication/fi_otbn_commands.py +++ b/target/communication/fi_otbn_commands.py @@ -12,6 +12,7 @@ class OTFIOtbn: + def __init__(self, target) -> None: self.target = target @@ -59,7 +60,8 @@ def otbn_char_jal(self) -> None: time.sleep(0.01) self.target.write(json.dumps("CharJal").encode("ascii")) - def otbn_char_mem(self, byte_offset, num_words, imem, dmem, first_call) -> None: + def otbn_char_mem(self, byte_offset, num_words, imem, dmem, + first_call) -> None: """Starts the otbn.fi.char.mem test.""" # OtbnFi command. self._ujson_otbn_fi_cmd() diff --git a/target/communication/fi_otp_commands.py b/target/communication/fi_otp_commands.py index 2b2348af..175d333b 100644 --- a/target/communication/fi_otp_commands.py +++ b/target/communication/fi_otp_commands.py @@ -12,6 +12,7 @@ class OTFIOtp: + def __init__(self, target) -> None: self.target = target diff --git a/target/communication/fi_rng_commands.py b/target/communication/fi_rng_commands.py index 61e8a1c1..bc729fca 100644 --- a/target/communication/fi_rng_commands.py +++ b/target/communication/fi_rng_commands.py @@ -13,6 +13,7 @@ class OTFIRng: + def __init__(self, target) -> None: self.target = target @@ -136,7 +137,8 @@ def rng_entropy_bias(self) -> None: time.sleep(0.05) self.target.write(json.dumps("EntropySrcBias").encode("ascii")) - def rng_fw_overwrite(self, disable_health_check: Optional[bool] = False) -> None: + def rng_fw_overwrite(self, + disable_health_check: Optional[bool] = False) -> None: """Starts the rng_fw_overwrite test. Args: diff --git a/target/communication/fi_rom_commands.py b/target/communication/fi_rom_commands.py index bd090217..8ab17b51 100644 --- a/target/communication/fi_rom_commands.py +++ b/target/communication/fi_rom_commands.py @@ -12,6 +12,7 @@ class OTFIRom: + def __init__(self, target) -> None: self.target = target diff --git a/target/communication/sca_aes_commands.py b/target/communication/sca_aes_commands.py index 65887c4e..8f7b2c5a 100644 --- a/target/communication/sca_aes_commands.py +++ b/target/communication/sca_aes_commands.py @@ -13,6 +13,7 @@ class OTAES: + def __init__(self, target) -> None: self.target = target diff --git a/target/communication/sca_hmac_commands.py b/target/communication/sca_hmac_commands.py index 6675ce4f..f97aaa30 100644 --- a/target/communication/sca_hmac_commands.py +++ b/target/communication/sca_hmac_commands.py @@ -12,6 +12,7 @@ class OTHMAC: + def __init__(self, target) -> None: self.target = target @@ -204,9 +205,8 @@ def random_batch(self, num_segments: int, trigger: int): } self.target.write(json.dumps(mode).encode("ascii")) - def daisy_chain( - self, text: list[int], key: list[int], num_segments: int, trigger: int - ): + def daisy_chain(self, text: list[int], key: list[int], num_segments: int, + trigger: int): """Start num_segments HMAC operations in daisy chain mode. Args: text: The input message diff --git a/target/communication/sca_ibex_commands.py b/target/communication/sca_ibex_commands.py index 6d49ab9d..df3aacd6 100644 --- a/target/communication/sca_ibex_commands.py +++ b/target/communication/sca_ibex_commands.py @@ -12,6 +12,7 @@ class OTIbex: + def __init__(self, target) -> None: self.target = target @@ -81,7 +82,8 @@ def ibex_sca_register_file_read(self, data: list[int]): data = {"data": data} self.target.write(json.dumps(data).encode("ascii")) - def ibex_sca_register_file_read_batch_fvsr(self, data: int, num_segments: int): + def ibex_sca_register_file_read_batch_fvsr(self, data: int, + num_segments: int): """Start ibex.sca.register_file_read_batch_fvsr test. Args: data: The data that is first written into the RF and then read back. @@ -124,7 +126,8 @@ def ibex_sca_register_file_write(self, data: list[int]): data = {"data": data} self.target.write(json.dumps(data).encode("ascii")) - def ibex_sca_register_file_write_batch_fvsr(self, data: int, num_segments: int): + def ibex_sca_register_file_write_batch_fvsr(self, data: int, + num_segments: int): """Start ibex.sca.register_file_write_batch_fvsr test. Args: data: The data that is written into the RF. @@ -161,7 +164,8 @@ def ibex_sca_tl_write_batch_random_fix_address(self, num_segments: int): # IbexSca command. self._ujson_ibex_sca_cmd() # TLWriteBatchRandomFixAddress command. - self.target.write(json.dumps("TLWriteBatchRandomFixAddress").encode("ascii")) + self.target.write( + json.dumps("TLWriteBatchRandomFixAddress").encode("ascii")) # Data payload. time.sleep(0.01) data = {"num_iterations": num_segments} @@ -196,7 +200,8 @@ def ibex_sca_tl_write_batch_fvsr(self, data: int, num_segments: int): data = {"num_iterations": num_segments, "fixed_data": data} self.target.write(json.dumps(data).encode("ascii")) - def ibex_sca_tl_write_batch_fvsr_fix_address(self, data: int, num_segments: int): + def ibex_sca_tl_write_batch_fvsr_fix_address(self, data: int, + num_segments: int): """Start ibex.sca.tl_write_batch_fvsr_fix_address test. Args: data: The data that is written into the SRAM over Tl-UL. @@ -205,7 +210,8 @@ def ibex_sca_tl_write_batch_fvsr_fix_address(self, data: int, num_segments: int) # IbexSca command. self._ujson_ibex_sca_cmd() # TLWriteBatchFvsrFixAddress command. - self.target.write(json.dumps("TLWriteBatchFvsrFixAddress").encode("ascii")) + self.target.write( + json.dumps("TLWriteBatchFvsrFixAddress").encode("ascii")) # Data payload. time.sleep(0.01) data = {"num_iterations": num_segments, "fixed_data": data} @@ -233,7 +239,8 @@ def ibex_sca_tl_read_batch_random_fix_address(self, num_segments: int): # IbexSca command. self._ujson_ibex_sca_cmd() # TLReadBatchRandomFixAddress command. - self.target.write(json.dumps("TLReadBatchRandomFixAddress").encode("ascii")) + self.target.write( + json.dumps("TLReadBatchRandomFixAddress").encode("ascii")) # Data payload. time.sleep(0.01) data = {"num_iterations": num_segments} @@ -269,7 +276,8 @@ def ibex_sca_tl_read_batch_fvsr(self, data: int, num_segments: int): data = {"num_iterations": num_segments, "fixed_data": data} self.target.write(json.dumps(data).encode("ascii")) - def ibex_sca_tl_read_batch_fvsr_fix_address(self, data: int, num_segments: int): + def ibex_sca_tl_read_batch_fvsr_fix_address(self, data: int, + num_segments: int): """Start ibex.sca.tl_read_batch_fvsr_fix_address test. Args: data: The data that is written into the SRAM over Tl-UL. @@ -278,15 +286,16 @@ def ibex_sca_tl_read_batch_fvsr_fix_address(self, data: int, num_segments: int): # IbexSca command. self._ujson_ibex_sca_cmd() # TLReadBatchFvsrFixAddress command. - self.target.write(json.dumps("TLReadBatchFvsrFixAddress").encode("ascii")) + self.target.write( + json.dumps("TLReadBatchFvsrFixAddress").encode("ascii")) # Data payload. time.sleep(0.01) data = {"num_iterations": num_segments, "fixed_data": data} self.target.write(json.dumps(data).encode("ascii")) - def ibex_sca_combi_operations_batch_fvsr( - self, num_iterations: int, trigger: int, fixed_data1: int, fixed_data2: int - ): + def ibex_sca_combi_operations_batch_fvsr(self, num_iterations: int, + trigger: int, fixed_data1: int, + fixed_data2: int): """Start ibex.sca.combi_operations_batch_fvsr test. Args: num_iterations: The number of iterations the test is repeated. @@ -297,7 +306,8 @@ def ibex_sca_combi_operations_batch_fvsr( # IbexSca command. self._ujson_ibex_sca_cmd() # CombiOperationsBatchFvsr command. - self.target.write(json.dumps("CombiOperationsBatchFvsr").encode("ascii")) + self.target.write( + json.dumps("CombiOperationsBatchFvsr").encode("ascii")) # Input payload. time.sleep(0.01) data = { @@ -308,9 +318,9 @@ def ibex_sca_combi_operations_batch_fvsr( } self.target.write(json.dumps(data).encode("ascii")) - def ibex_sca_combi_operations_batch( - self, num_iterations: int, trigger: int, fixed_data1: int, fixed_data2: int - ): + def ibex_sca_combi_operations_batch(self, num_iterations: int, + trigger: int, fixed_data1: int, + fixed_data2: int): """Start ibex.sca.combi_operations_batch test. Args: num_iterations: The number of iterations the test is repeated. @@ -332,9 +342,12 @@ def ibex_sca_combi_operations_batch( } self.target.write(json.dumps(data).encode("ascii")) - def start_test( - self, testname: str, arg1=None, arg2=None, arg3=None, arg4=None - ) -> None: + def start_test(self, + testname: str, + arg1=None, + arg2=None, + arg3=None, + arg4=None) -> None: """Start the selected test. Call the function selected in the config file. Uses the getattr() diff --git a/target/communication/sca_kmac_commands.py b/target/communication/sca_kmac_commands.py index c515412a..7c6b13ed 100644 --- a/target/communication/sca_kmac_commands.py +++ b/target/communication/sca_kmac_commands.py @@ -14,6 +14,7 @@ class OTKMAC: + def __init__(self, target) -> None: self.target = target diff --git a/target/communication/sca_otbn_commands.py b/target/communication/sca_otbn_commands.py index 325a8347..c4f6bf5d 100644 --- a/target/communication/sca_otbn_commands.py +++ b/target/communication/sca_otbn_commands.py @@ -13,6 +13,7 @@ class OTOTBN: + def __init__(self, target) -> None: self.target = target @@ -96,7 +97,8 @@ def ecdsa_p256_sign(self, masking_on: bool, msg, d0, k0): self.target.write(json.dumps(data).encode("ascii")) time.sleep(0.01) - def ecdsa_p256_sign_batch(self, num_traces: int, masking_on: bool, msg, d0, k0): + def ecdsa_p256_sign_batch(self, num_traces: int, masking_on: bool, msg, d0, + k0): """Starts the EcdsaP256SignBatch test on OTBN. Args: num_traces: Number of batch operations. @@ -123,9 +125,8 @@ def ecdsa_p256_sign_batch(self, num_traces: int, masking_on: bool, msg, d0, k0): self.target.write(json.dumps(data).encode("ascii")) time.sleep(0.01) - def ecdsa_p256_sign_batch_fvsr( - self, num_traces: int, masking_on: bool, msg, d0, k0 - ): + def ecdsa_p256_sign_batch_fvsr(self, num_traces: int, masking_on: bool, + msg, d0, k0): """Starts the EcdsaP256SignFvsrBatch test on OTBN. Args: num_traces: Number of batch operations. @@ -152,9 +153,8 @@ def ecdsa_p256_sign_batch_fvsr( self.target.write(json.dumps(data).encode("ascii")) time.sleep(0.01) - def start_combi_ops_batch( - self, num_iterations, fixed_data1, fixed_data2, print_flag, trigger - ): + def start_combi_ops_batch(self, num_iterations, fixed_data1, fixed_data2, + print_flag, trigger): """Start the combi ops app in batch mode. Args: num_iterations: How many traces per batch. diff --git a/target/communication/sca_prng_commands.py b/target/communication/sca_prng_commands.py index cc821aa9..a9252c5e 100644 --- a/target/communication/sca_prng_commands.py +++ b/target/communication/sca_prng_commands.py @@ -12,6 +12,7 @@ class OTPRNG: + def __init__(self, target) -> None: self.target = target diff --git a/target/communication/sca_sha3_commands.py b/target/communication/sca_sha3_commands.py index 442c942e..3e663738 100644 --- a/target/communication/sca_sha3_commands.py +++ b/target/communication/sca_sha3_commands.py @@ -14,6 +14,7 @@ class OTSHA3: + def __init__(self, target) -> None: self.target = target @@ -76,7 +77,8 @@ def _ujson_sha3_sca_ack(self, num_attempts: Optional[int] = 100): ) return status except Exception: - raise Exception("Acknowledge error: Device and host not in sync") + raise Exception( + "Acknowledge error: Device and host not in sync") else: read_counter += 1 raise Exception("Acknowledge error: Device and host not in sync") diff --git a/target/communication/sca_trigger_commands.py b/target/communication/sca_trigger_commands.py index c25bff50..bac40919 100644 --- a/target/communication/sca_trigger_commands.py +++ b/target/communication/sca_trigger_commands.py @@ -12,6 +12,7 @@ class OTTRIGGER: + def __init__(self, target) -> None: self.target = target diff --git a/target/cw_fpga.py b/target/cw_fpga.py index 855e1003..63a3a816 100644 --- a/target/cw_fpga.py +++ b/target/cw_fpga.py @@ -76,7 +76,8 @@ def __init__( self.fpga_type = cw.capture.targets.CW310() programmer = SpiProgrammer(self.fpga_type, self.usb_serial) else: - raise ValueError("Could not infer target board type from bistream name") + raise ValueError( + "Could not infer target board type from bistream name") # Initialize ChipWhisperer scope. This is needed to program the binary. # Note that the actual scope config for capturing traces is later # initialized. @@ -85,15 +86,14 @@ def __init__( # Initializing the scope twice seems to solve the problem. self.scope = cw.scope(sn=self.husky_serial) - self.fpga = self.initialize_fpga( - self.fpga_type, bitstream, force_programming, pll_frequency - ) + self.fpga = self.initialize_fpga(self.fpga_type, bitstream, + force_programming, pll_frequency) - self.target = self.initialize_target( - programmer, firmware, baudrate, pll_frequency - ) + self.target = self.initialize_target(programmer, firmware, baudrate, + pll_frequency) - def initialize_fpga(self, fpga, bitstream, force_programming, pll_frequency): + def initialize_fpga(self, fpga, bitstream, force_programming, + pll_frequency): """Initializes FPGA bitstream and sets PLL frequency.""" # Do not program the FPGA if it is already programmed. # Note: Set this to True to force programming the FPGA when using a new @@ -159,7 +159,8 @@ def initialize_target(self, programmer, firmware, baudrate, pll_frequency): time.sleep(0.5) target = cw.target(self.scope) - target.baud = int(self.baudrate * pll_frequency / PLL_FREQUENCY_DEFAULT) + target.baud = int(self.baudrate * pll_frequency / + PLL_FREQUENCY_DEFAULT) target.flush() return target @@ -193,9 +194,8 @@ def reset_target(self): self.scope = cw.scope(sn=self.husky_serial) - self.fpga = self.initialize_fpga( - self.fpga_type, self.bitstream, True, self.pll_frequency - ) + self.fpga = self.initialize_fpga(self.fpga_type, self.bitstream, + True, self.pll_frequency) self.target = self.initialize_target( programmer, diff --git a/target/targets.py b/target/targets.py index 65df5bc4..65446dad 100644 --- a/target/targets.py +++ b/target/targets.py @@ -53,9 +53,8 @@ def __init__(self, target_cfg: TargetConfig) -> None: if target_cfg.baudrate is None: target_cfg.baudrate = 115200 - self.com_interface = self._init_communication( - self.find_target_port(), self.target_cfg.baudrate - ) + self.com_interface = self._init_communication(self.find_target_port(), + self.target_cfg.baudrate) if target_cfg.fw_bin is not None: self.target = self._init_target() @@ -66,10 +65,8 @@ def _init_target(self): Configure OpenTitan on CW FPGA or the discrete chip. """ target = None - if ( - self.target_cfg.target_type == "cw305" or - self.target_cfg.target_type == "cw310" - ): + if (self.target_cfg.target_type == "cw305" or + self.target_cfg.target_type == "cw310"): target = CWFPGA( bitstream=self.target_cfg.bitstream, force_programming=self.target_cfg.force_program_bitstream, @@ -77,7 +74,7 @@ def _init_target(self): pll_frequency=self.target_cfg.pll_frequency, baudrate=self.target_cfg.baudrate, usb_serial=self.target_cfg.usb_serial, - husky_serial = self.target_cfg.husky_serial, + husky_serial=self.target_cfg.husky_serial, ) elif self.target_cfg.target_type == "chip": target = Chip(opentitantool_path=self.target_cfg.opentitantool) @@ -133,8 +130,7 @@ def reset_target(self, com_reset: Optional[bool] = False): self.target.reset_target() if com_reset: self.com_interface = self._init_communication( - self.find_target_port(), self.target_cfg.baudrate - ) + self.find_target_port(), self.target_cfg.baudrate) def write(self, data, cmd: Optional[str] = ""): """Write data to the target.""" @@ -146,10 +142,8 @@ def readline(self): def is_done(self): """Check if target is done. Only for CWFPGA.""" - if ( - self.target_cfg.target_type == "cw305" or - self.target_cfg.target_type == "cw310" - ): + if (self.target_cfg.target_type == "cw305" or + self.target_cfg.target_type == "cw310"): return self.target.target.is_done() else: return True @@ -188,7 +182,8 @@ def check_fault_or_read_reponse(self, max_tries=50): if "FAULT" in read_line: return read_line, False if "RESP_OK" in read_line: - return read_line.split("RESP_OK:")[1].split(" CRC:")[0], True + return read_line.split("RESP_OK:")[1].split( + " CRC:")[0], True it += 1 except UnicodeDecodeError: it += 1 @@ -211,7 +206,8 @@ def check_reset_or_read_reponse(self, max_tries=50): if "Chip flashed" in read_line: return read_line, False if "RESP_OK" in read_line: - return read_line.split("RESP_OK:")[1].split(" CRC:")[0], True + return read_line.split("RESP_OK:")[1].split( + " CRC:")[0], True it += 1 except UnicodeDecodeError: it += 1 diff --git a/test/cmd.py b/test/cmd.py index 3b27702f..e9e527cc 100644 --- a/test/cmd.py +++ b/test/cmd.py @@ -1,7 +1,6 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - """Utilities to run command-line programs and interpret and validate their results.""" import subprocess @@ -61,17 +60,16 @@ def run(self) -> "Cmd": If the expected returncode is not None, assert that it matches the actual returncode. """ - self._proc = subprocess.Popen( - self._args, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + self._proc = subprocess.Popen(self._args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) self._stdout, self._stderr = self._proc.communicate() self._returncode = self._proc.returncode if self._exp_returncode is not None: assert self._returncode == self._exp_returncode, ( f"{self._args} returned {self._returncode} instead of {self._exp_returncode}, " - f"with the following stderr:\n{self.stderr_utf8()}" - ) + f"with the following stderr:\n{self.stderr_utf8()}") return self diff --git a/test/repo.py b/test/repo.py index c5cfcfe8..6c2e03a1 100644 --- a/test/repo.py +++ b/test/repo.py @@ -1,7 +1,6 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - """Utilities to run tests in the context of this repository.""" from pathlib import Path @@ -13,6 +12,7 @@ class RepoCmd(Cmd): + def __init__(self, args: Args): # Prepend absolute path to repository to the command. args[0] = str(REPO_PATH / args[0]) diff --git a/test/tvla_test.py b/test/tvla_test.py index e024e46e..34d52348 100644 --- a/test/tvla_test.py +++ b/test/tvla_test.py @@ -9,6 +9,7 @@ class TvlaCmd(RepoCmd): + def __init__(self, args: Args): # Insert (relative) path to TVLA before the given arguments. args = Args("analysis/tvla.py") + args @@ -37,29 +38,26 @@ def ttest_compare_results(expected, received, delta) -> bool: """ nan_match = np.all(np.isnan(expected) == np.isnan(received)) numbers_match = np.all( - np.logical_or(np.isnan(expected), np.abs(expected - received) < delta) - ) + np.logical_or(np.isnan(expected), + np.abs(expected - received) < delta)) return nan_match and numbers_match def test_general_kmac_nonleaking_project(): project_path = TestDataPath("tvla_general/ci_opentitan_simple_kmac.cwp") tvla = TvlaCmd( - Args( - [ - "--project-file", - str(project_path), - "--mode", - "kmac", - "--save-to-disk-ttest", - "--test-type", - "GENERAL_KEY", - "--number-of-steps", - "10", - "run-tvla", - ] - ) - ).run() + Args([ + "--project-file", + str(project_path), + "--mode", + "kmac", + "--save-to-disk-ttest", + "--test-type", + "GENERAL_KEY", + "--number-of-steps", + "10", + "run-tvla", + ])).run() expected_path = TestDataPath("tvla_general/ttest-step-golden-kmac.npy.npz") expected_file = np.load(str(expected_path)) expected_trace = expected_file["ttest_step"] @@ -75,23 +73,21 @@ def test_general_kmac_nonleaking_project(): def test_general_aes_nonleaking_project(): - project_path = TestDataPath("tvla_general/ci_opentitan_simple_aes_fvsr.cwp") + project_path = TestDataPath( + "tvla_general/ci_opentitan_simple_aes_fvsr.cwp") tvla = TvlaCmd( - Args( - [ - "--project-file", - str(project_path), - "--mode", - "aes", - "--save-to-disk-ttest", - "--test-type", - "GENERAL_KEY", - "--number-of-steps", - "10", - "run-tvla", - ] - ) - ).run() + Args([ + "--project-file", + str(project_path), + "--mode", + "aes", + "--save-to-disk-ttest", + "--test-type", + "GENERAL_KEY", + "--number-of-steps", + "10", + "run-tvla", + ])).run() expected_path = TestDataPath("tvla_general/ttest-step-golden-aes.npy.npz") expected_file = np.load(str(expected_path)) expected_trace = expected_file["ttest_step"] @@ -106,19 +102,16 @@ def test_general_aes_nonleaking_project(): def test_general_leaking_histogram(): hist_path = TestDataPath("tvla_general/kmac_hist_leaking.npz") tvla = TvlaCmd( - Args( - [ - "--input-histogram-file", - str(hist_path), - "--mode", - "kmac", - "--save-to-disk-ttest", - "--test-type", - "GENERAL_KEY", - "run-tvla", - ] - ) - ).run() + Args([ + "--input-histogram-file", + str(hist_path), + "--mode", + "kmac", + "--save-to-disk-ttest", + "--test-type", + "GENERAL_KEY", + "run-tvla", + ])).run() assert ttest_significant( np.load("tmp/ttest.npy") ), f"{tvla} did not find significant leakage, which is unexpected" @@ -127,44 +120,38 @@ def test_general_leaking_histogram(): def test_general_nonleaking_histogram(): hist_path = TestDataPath("tvla_general/kmac_hist_nonleaking.npz") tvla = TvlaCmd( - Args( - [ - "--input-histogram-file", - str(hist_path), - "--mode", - "kmac", - "--save-to-disk-ttest", - "--test-type", - "GENERAL_KEY", - "run-tvla", - ] - ) - ).run() + Args([ + "--input-histogram-file", + str(hist_path), + "--mode", + "kmac", + "--save-to-disk-ttest", + "--test-type", + "GENERAL_KEY", + "run-tvla", + ])).run() assert not ttest_significant( - np.load("tmp/ttest.npy") - ), f"{tvla} did find significant leakage, which is unexpected" + np.load("tmp/ttest.npy" + )), f"{tvla} did find significant leakage, which is unexpected" def test_aes_byte_filtering(): project_path = TestDataPath("tvla_aes_byte/ci_opentitan_simple_aes.cwp") tvla = TvlaCmd( - Args( - [ - "--project-file", - str(project_path), - "--mode", - "aes", - "--round-select", - "0", - "--byte-select", - "0", - "--save-to-disk", - "--test-type", - "SPECIFIC_BYTE", - "run-tvla", - ] - ) - ).run() + Args([ + "--project-file", + str(project_path), + "--mode", + "aes", + "--round-select", + "0", + "--byte-select", + "0", + "--save-to-disk", + "--test-type", + "SPECIFIC_BYTE", + "run-tvla", + ])).run() received_file = np.load("tmp/traces.npy.npz") traces_to_use = received_file["traces_to_use"] assert ( diff --git a/util/check_version.py b/util/check_version.py index c2faea10..948a16a6 100644 --- a/util/check_version.py +++ b/util/check_version.py @@ -22,8 +22,7 @@ def check_cw(cw_version_exp: str) -> None: if cw_version != cw_version_exp: raise RuntimeError( f"Please update the Python requirements. CW version: \ - {cw_version}, expected CW version: {cw_version_exp}" - ) # noqa: E501 + {cw_version}, expected CW version: {cw_version_exp}") # noqa: E501 def check_husky(husky_fw_exp: str, sn: Optional[str] = None) -> None: @@ -44,5 +43,4 @@ def check_husky(husky_fw_exp: str, sn: Optional[str] = None) -> None: if husky_fw != husky_fw_exp: raise RuntimeError( f"Please update the Husky firmware. FW version: {husky_fw}, \ - expected FW version: {husky_fw_exp}" - ) # noqa: E501 + expected FW version: {husky_fw_exp}") # noqa: E501 diff --git a/util/cw_to_trs.py b/util/cw_to_trs.py index 37510dde..ddb154c9 100755 --- a/util/cw_to_trs.py +++ b/util/cw_to_trs.py @@ -29,21 +29,34 @@ def gen_trs_headers(project, export_key): trs file header. """ return { - trsfile.Header.LABEL_X: "s", - trsfile.Header.LABEL_Y: "V", - trsfile.Header.NUMBER_SAMPLES: len(project.waves[0]), - trsfile.Header.TRACE_TITLE: "title", - trsfile.Header.TRACE_OVERLAP: False, - trsfile.Header.GO_LAST_TRACE: False, - trsfile.Header.SAMPLE_CODING: trsfile.SampleCoding.FLOAT, - trsfile.Header.LENGTH_DATA: len(gen_trs_data(project.traces[0], export_key)), + trsfile.Header.LABEL_X: + "s", + trsfile.Header.LABEL_Y: + "V", + trsfile.Header.NUMBER_SAMPLES: + len(project.waves[0]), + trsfile.Header.TRACE_TITLE: + "title", + trsfile.Header.TRACE_OVERLAP: + False, + trsfile.Header.GO_LAST_TRACE: + False, + trsfile.Header.SAMPLE_CODING: + trsfile.SampleCoding.FLOAT, + trsfile.Header.LENGTH_DATA: + len(gen_trs_data(project.traces[0], export_key)), # TODO: Hardcoded to 200mV. Consider calculating the range directly from # the traces. - trsfile.Header.ACQUISITION_RANGE_OF_SCOPE: 0.200, - trsfile.Header.ACQUISITION_COUPLING_OF_SCOPE: 1, - trsfile.Header.ACQUISITION_OFFSET_OF_SCOPE: 0.0, - trsfile.Header.ACQUISITION_DEVICE_ID: b"CWHusky", - trsfile.Header.ACQUISITION_TYPE_FILTER: 0, + trsfile.Header.ACQUISITION_RANGE_OF_SCOPE: + 0.200, + trsfile.Header.ACQUISITION_COUPLING_OF_SCOPE: + 1, + trsfile.Header.ACQUISITION_OFFSET_OF_SCOPE: + 0.0, + trsfile.Header.ACQUISITION_DEVICE_ID: + b"CWHusky", + trsfile.Header.ACQUISITION_TYPE_FILTER: + 0, } @@ -63,19 +76,18 @@ def calc_data_offsets(trace, export_key, header): key_offset = output_offset + output_len key_len = len(trace.key) - header.update( - { - trsfile.Header.INPUT_OFFSET: input_offset, - trsfile.Header.INPUT_LENGTH: input_len, - trsfile.Header.OUTPUT_OFFSET: output_offset, - trsfile.Header.OUTPUT_LENGTH: output_len, - } - ) + header.update({ + trsfile.Header.INPUT_OFFSET: input_offset, + trsfile.Header.INPUT_LENGTH: input_len, + trsfile.Header.OUTPUT_OFFSET: output_offset, + trsfile.Header.OUTPUT_LENGTH: output_len, + }) if export_key: - header.update( - {trsfile.Header.KEY_OFFSET: key_offset, trsfile.Header.KEY_LENGTH: key_len} - ) + header.update({ + trsfile.Header.KEY_OFFSET: key_offset, + trsfile.Header.KEY_LENGTH: key_len + }) def gen_trs_data(trace, export_key): @@ -117,28 +129,34 @@ def cw_project_to_trs(project_name, trs_filename, export_keys): trsfile.SampleCoding.FLOAT, trace.wave, data=gen_trs_data(trace, export_keys), - ) - ) + )) print("Writing output file, this may take a while.") - with trsfile.trs_open( - trs_filename, "w", engine="TrsEngine", headers=h, live_update=True - ) as t: + with trsfile.trs_open(trs_filename, + "w", + engine="TrsEngine", + headers=h, + live_update=True) as t: t.extend(traces) def parse_args(): """Parse command line arguments.""" parser = argparse.ArgumentParser() - parser.add_argument( - "--input", "-i", type=str, required=True, help="Input ChipWhisperer project." - ) - parser.add_argument( - "--output", "-o", type=str, required=True, help="Output trs filename." - ) - parser.add_argument( - "--export-key", "-k", action="store_true", help="Include keys in data output." - ) + parser.add_argument("--input", + "-i", + type=str, + required=True, + help="Input ChipWhisperer project.") + parser.add_argument("--output", + "-o", + type=str, + required=True, + help="Output trs filename.") + parser.add_argument("--export-key", + "-k", + action="store_true", + help="Include keys in data output.") args = parser.parse_args() return args diff --git a/util/data_generator.py b/util/data_generator.py index 53f9bdcd..2f0539cb 100644 --- a/util/data_generator.py +++ b/util/data_generator.py @@ -1,10 +1,6 @@ # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 - -from Crypto.Cipher import AES -from Crypto.Hash import KMAC128, SHA3_256 - """Data generator. Generates crypto material for the SCA tests. @@ -13,45 +9,74 @@ and key) is plain integer arrays. """ +from Crypto.Cipher import AES +from Crypto.Hash import KMAC128, SHA3_256 -class data_generator(): - key_generation = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF1, 0x23, - 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xE0, 0xF0] +class data_generator(): - text_fixed = [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, - 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA] - text_random = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, - 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC] - key_fixed = [0x81, 0x1E, 0x37, 0x31, 0xB0, 0x12, 0x0A, 0x78, 0x42, 0x78, - 0x1E, 0x22, 0xB2, 0x5C, 0xDD, 0xF9] - key_random = [0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, - 0x53, 0x53, 0x53, 0x53, 0x53, 0x53] + key_generation = [ + 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF1, 0x23, 0x45, 0x67, 0x89, + 0xAB, 0xCD, 0xE0, 0xF0 + ] + + text_fixed = [ + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA + ] + text_random = [ + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0xCC + ] + key_fixed = [ + 0x81, 0x1E, 0x37, 0x31, 0xB0, 0x12, 0x0A, 0x78, 0x42, 0x78, 0x1E, 0x22, + 0xB2, 0x5C, 0xDD, 0xF9 + ] + key_random = [ + 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, + 0x53, 0x53, 0x53, 0x53 + ] cipher_gen = AES.new(bytes(key_generation), AES.MODE_ECB) def __init__(self): self.cipher_gen = AES.new(bytes(self.key_generation), AES.MODE_ECB) - def set_start(self, capture_type = 'FVSR_KEY'): + def set_start(self, capture_type='FVSR_KEY'): if capture_type == 'FVSR_KEY': - self.text_fixed = [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, - 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA] - self.text_random = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, - 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC] - self.key_fixed = [0x81, 0x1E, 0x37, 0x31, 0xB0, 0x12, 0x0A, 0x78, 0x42, - 0x78, 0x1E, 0x22, 0xB2, 0x5C, 0xDD, 0xF9] - self.key_random = [0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, - 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53] + self.text_fixed = [ + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA + ] + self.text_random = [ + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC + ] + self.key_fixed = [ + 0x81, 0x1E, 0x37, 0x31, 0xB0, 0x12, 0x0A, 0x78, 0x42, 0x78, + 0x1E, 0x22, 0xB2, 0x5C, 0xDD, 0xF9 + ] + self.key_random = [ + 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, + 0x53, 0x53, 0x53, 0x53, 0x53, 0x53 + ] else: - self.text_fixed = [0xDA, 0x39, 0xA3, 0xEE, 0x5E, 0x6B, 0x4B, 0x0D, - 0x32, 0x55, 0xBF, 0xEF, 0x95, 0x60, 0x18, 0x90] - self.text_random = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] - self.key_fixed = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, - 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] - self.key_random = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, - 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0] + self.text_fixed = [ + 0xDA, 0x39, 0xA3, 0xEE, 0x5E, 0x6B, 0x4B, 0x0D, 0x32, 0x55, + 0xBF, 0xEF, 0x95, 0x60, 0x18, 0x90 + ] + self.text_random = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ] + self.key_fixed = [ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x12, 0x34, + 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 + ] + self.key_random = [ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x12, 0x34, + 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0 + ] def advance_fixed(self): text_fixed_bytes = self.cipher_gen.encrypt(bytes(self.text_fixed)) @@ -71,7 +96,7 @@ def advance_random_data(self): # Convert bytearray into int array. self.text_random = [x for x in text_random_bytes] - def get_fixed(self, capture_type = 'FVSR_KEY'): + def get_fixed(self, capture_type='FVSR_KEY'): pt = self.text_fixed key = self.key_fixed cipher_fixed = AES.new(bytes(self.key_fixed), AES.MODE_ECB) @@ -82,7 +107,7 @@ def get_fixed(self, capture_type = 'FVSR_KEY'): self.advance_fixed() return pt, ct, key - def get_random(self, capture_type = 'FVSR_KEY'): + def get_random(self, capture_type='FVSR_KEY'): pt = self.text_random key = self.key_random cipher_random = AES.new(bytes(self.key_random), AES.MODE_ECB) diff --git a/util/db_to_zarr_converter.py b/util/db_to_zarr_converter.py index 76768344..f9b3bfd0 100644 --- a/util/db_to_zarr_converter.py +++ b/util/db_to_zarr_converter.py @@ -131,9 +131,8 @@ def main(argv=None): ) trace_end = 0 - for trace_it in tqdm( - range(0, num_traces, args.max_traces_mem), desc="Converting trace" - ): + for trace_it in tqdm(range(0, num_traces, args.max_traces_mem), + desc="Converting trace"): trace_end += args.max_traces_mem # Fetch trace, plaintext, ciphertext, and key from DB. in_traces = np.array(project_in.get_waves(trace_it, trace_end)) diff --git a/util/fixlintpy.sh b/util/fixlintpy.sh new file mode 100755 index 00000000..048378d4 --- /dev/null +++ b/util/fixlintpy.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Lint all Python sources using the specified lint tools +FILE_PATHS=`find . -not \( -path './.venv' -prune \) \ + -not \( -path './util/vendor' -prune \) \ + -name '*.py'` + +./util/vendor/lowrisc_opentitan/lintpy.py -f ${FILE_PATHS} --tools yapf,isort --fix diff --git a/util/histograms.py b/util/histograms.py index a6f2be70..b026a9c6 100644 --- a/util/histograms.py +++ b/util/histograms.py @@ -28,13 +28,15 @@ def compute_histograms_general(trace_resolution, traces, leakage): histograms[0, 0, :, i_sample, :] = np.histogram2d( leakage, traces[:, i_sample], - bins=[range(num_leakages + 1), range(trace_resolution + 1)], + bins=[range(num_leakages + 1), + range(trace_resolution + 1)], )[0] return histograms -def compute_histograms_aes_byte(trace_resolution, rnd_list, byte_list, traces, leakage): +def compute_histograms_aes_byte(trace_resolution, rnd_list, byte_list, traces, + leakage): """Building histograms for AES. For each time sample we make two histograms, one for Hamming weight of the sensitive variable @@ -66,7 +68,8 @@ def compute_histograms_aes_byte(trace_resolution, rnd_list, byte_list, traces, l return histograms -def compute_histograms_aes_bit(trace_resolution, rnd_list, bit_list, traces, leakage): +def compute_histograms_aes_bit(trace_resolution, rnd_list, bit_list, traces, + leakage): """Building histograms for AES. For each time sample we make two histograms, one for selected bit value = 0 (fixed set) and one @@ -89,7 +92,10 @@ def compute_histograms_aes_bit(trace_resolution, rnd_list, bit_list, traces, lea histograms[i_rnd, i_bit, :, i_sample, :] = np.histogram2d( leakage[rnd_list[i_rnd], bit_list[i_bit], :], traces[:, i_sample], - bins=[range(num_leakages + 1), range(trace_resolution + 1)], + bins=[ + range(num_leakages + 1), + range(trace_resolution + 1) + ], )[0] return histograms diff --git a/util/leakage_models.py b/util/leakage_models.py index 11be8225..259b3f3e 100644 --- a/util/leakage_models.py +++ b/util/leakage_models.py @@ -47,14 +47,16 @@ def compute_leakage_aes_byte(keys, plaintexts, leakage_model="HAMMING_WEIGHT"): if key_fixed: for j in range(11): - subkey[j] = np.asarray(aes_funcs.key_schedule_rounds(keys[0], 0, j)) + subkey[j] = np.asarray(aes_funcs.key_schedule_rounds( + keys[0], 0, j)) subkey = subkey.astype(int) for i in range(num_traces): if not key_fixed: for j in range(11): - subkey[j] = np.asarray(aes_funcs.key_schedule_rounds(keys[i], 0, j)) + subkey[j] = np.asarray( + aes_funcs.key_schedule_rounds(keys[i], 0, j)) subkey = subkey.astype(int) # Init @@ -65,7 +67,8 @@ def compute_leakage_aes_byte(keys, plaintexts, leakage_model="HAMMING_WEIGHT"): state = np.bitwise_xor(state, subkey[0]) for k in range(16): if leakage_model == "HAMMING_DISTANCE": - leakage[0][k][i] = bit_count(np.bitwise_xor(state[k], old_state[k])) + leakage[0][k][i] = bit_count( + np.bitwise_xor(state[k], old_state[k])) else: leakage[0][k][i] = bit_count(state[k]) @@ -79,7 +82,8 @@ def compute_leakage_aes_byte(keys, plaintexts, leakage_model="HAMMING_WEIGHT"): state = np.bitwise_xor(state, subkey[j]) for k in range(16): if leakage_model == "HAMMING_DISTANCE": - leakage[j][k][i] = bit_count(np.bitwise_xor(state[k], old_state[k])) + leakage[j][k][i] = bit_count( + np.bitwise_xor(state[k], old_state[k])) else: leakage[j][k][i] = bit_count(state[k]) @@ -106,14 +110,16 @@ def compute_leakage_aes_bit(keys, plaintexts, leakage_model="HAMMING_WEIGHT"): if key_fixed: for j in range(11): - subkey[j] = np.asarray(aes_funcs.key_schedule_rounds(keys[0], 0, j)) + subkey[j] = np.asarray(aes_funcs.key_schedule_rounds( + keys[0], 0, j)) subkey = subkey.astype(int) for i in range(num_traces): if not key_fixed: for j in range(11): - subkey[j] = np.asarray(aes_funcs.key_schedule_rounds(keys[i], 0, j)) + subkey[j] = np.asarray( + aes_funcs.key_schedule_rounds(keys[i], 0, j)) subkey = subkey.astype(int) # Init @@ -127,7 +133,7 @@ def compute_leakage_aes_bit(keys, plaintexts, leakage_model="HAMMING_WEIGHT"): vec8 = byte2bits(np.bitwise_xor(state[k], old_state[k])) else: vec8 = byte2bits(state[k]) - leakage[0][8 * k: 8 * k + 8, i] = vec8 + leakage[0][8 * k:8 * k + 8, i] = vec8 # Round 1 - 10 for j in range(1, 11): @@ -142,7 +148,7 @@ def compute_leakage_aes_bit(keys, plaintexts, leakage_model="HAMMING_WEIGHT"): vec8 = byte2bits(np.bitwise_xor(state[k], old_state[k])) else: vec8 = byte2bits(state[k]) - leakage[j][8 * k: 8 * k + 8, i] = vec8 + leakage[j][8 * k:8 * k + 8, i] = vec8 return leakage diff --git a/util/plot.py b/util/plot.py index e6ad3793..28406bce 100644 --- a/util/plot.py +++ b/util/plot.py @@ -15,9 +15,12 @@ from fault_injection.project_library.project import FISuccess -def save_plot_to_file( - traces, set_indices, num_traces, outfile, add_mean_stddev=False, ref_trace=None -): +def save_plot_to_file(traces, + set_indices, + num_traces, + outfile, + add_mean_stddev=False, + ref_trace=None): """Save plot figure to file.""" if set_indices is None: colors = itertools.cycle(palette) @@ -49,9 +52,11 @@ def save_plot_to_file( ) if ref_trace is not None: - plot.line( - xrange, ref_trace, line_color="firebrick", line_width=2, legend_label="mean" - ) + plot.line(xrange, + ref_trace, + line_color="firebrick", + line_width=2, + legend_label="mean") if add_mean_stddev: # Add mean and std dev to figure @@ -77,7 +82,11 @@ def save_plot_to_file( line_width=2, legend_label="std", ) - plot.line(xrange, mean, line_color="black", line_width=2, legend_label="mean") + plot.line(xrange, + mean, + line_color="black", + line_width=2, + legend_label="mean") output_file(Path(str(outfile) + ".html")) show(plot) @@ -98,11 +107,15 @@ def save_fi_plot_to_file(cfg: dict, fi_results: [], outfile: str) -> None: y_axis = cfg["fiproject"]["plot_y_axis"] plot = figure( plot_width=800, - x_range=(cfg["fisetup"][x_axis + "_min"], cfg["fisetup"][x_axis + "_max"]), - y_range=(cfg["fisetup"][y_axis + "_min"], cfg["fisetup"][y_axis + "_max"]), + x_range=(cfg["fisetup"][x_axis + "_min"], + cfg["fisetup"][x_axis + "_max"]), + y_range=(cfg["fisetup"][y_axis + "_min"], + cfg["fisetup"][y_axis + "_max"]), ) - plot.xaxis.axis_label = x_axis + " " + cfg["fiproject"]["plot_x_axis_legend"] - plot.yaxis.axis_label = y_axis + " " + cfg["fiproject"]["plot_y_axis_legend"] + plot.xaxis.axis_label = x_axis + " " + cfg["fiproject"][ + "plot_x_axis_legend"] + plot.yaxis.axis_label = y_axis + " " + cfg["fiproject"][ + "plot_y_axis_legend"] exp_x = [] exp_y = [] @@ -139,8 +152,10 @@ def save_fi_plot_to_file(cfg: dict, fi_results: [], outfile: str) -> None: legend_label="Expected response", ) if no_x: - plot.scatter( - no_x, no_y, line_color="red", fill_color="red", legend_label="No response" - ) + plot.scatter(no_x, + no_y, + line_color="red", + fill_color="red", + legend_label="No response") show(plot) diff --git a/util/spiflash.py b/util/spiflash.py index b5c44646..143f79d3 100644 --- a/util/spiflash.py +++ b/util/spiflash.py @@ -96,9 +96,10 @@ def __init__(self, fpga, sn): self.io.pin_set_output(mapped_to) self.io.pin_set_state(mapped_to, getattr(self.INITIAL_VALUES, pin)) # Initialize SPI pins - self.io.spi1_setpins( - sck=self.pins.sck, sdo=self.pins.sdi, sdi=self.pins.sdo, cs=self.pins.cs - ) + self.io.spi1_setpins(sck=self.pins.sck, + sdo=self.pins.sdi, + sdi=self.pins.sdo, + cs=self.pins.cs) self.io.spi1_enable(True) def reset(self): @@ -158,7 +159,8 @@ def write_enable_and_page_program(self, addr, data): Also handles enabling writes and busy polling. """ self.write_enable() - packet = bytes([0x02]) + addr.to_bytes(3, byteorder="big", signed=False) + data + packet = bytes([0x02]) + addr.to_bytes( + 3, byteorder="big", signed=False) + data self.transceive(packet) self.busy_poll() @@ -183,6 +185,8 @@ def bootstrap(self, binary): # ends the loop, i.e. the value returned by ``f.read`` at EOF. addr = 0 for data in iter(partial(f.read, self.PAYLOAD_SIZE), b""): - print(f"Programming {len(data)} bytes at address 0x{addr:08x}.") + print( + f"Programming {len(data)} bytes at address 0x{addr:08x}." + ) self.write_enable_and_page_program(addr, data) addr += len(data) diff --git a/util/trace_util.py b/util/trace_util.py index ebdc5664..9fb7223d 100644 --- a/util/trace_util.py +++ b/util/trace_util.py @@ -11,17 +11,8 @@ def check_range(waves, bits_per_sample): adc_range = np.array([0, 2**bits_per_sample]) if not (np.all(np.greater(waves[:], adc_range[0])) and np.all(np.less(waves[:], adc_range[1] - 1))): - print( - "\nWARNING: Some samples are outside the range [" + - str(adc_range[0] + 1) + - ", " + - str(adc_range[1] - 2) + - "]." - ) - print( - "The ADC has a max range of [" + str(adc_range[0]) + - ", " + - str(adc_range[1] - 1) + - "] and might saturate." - ) + print("\nWARNING: Some samples are outside the range [" + + str(adc_range[0] + 1) + ", " + str(adc_range[1] - 2) + "].") + print("The ADC has a max range of [" + str(adc_range[0]) + ", " + + str(adc_range[1] - 1) + "] and might saturate.") print("It is recommended to reduce the scope gain.") diff --git a/util/ttest.py b/util/ttest.py index 44d45899..e68f39aa 100644 --- a/util/ttest.py +++ b/util/ttest.py @@ -54,9 +54,14 @@ def ttest1_hist_xy(x_a, y_a, x_b, y_b): std2 = np.sqrt(var_hist_xy(x_b, y_b, mu2)) N1 = np.sum(y_a, axis=1) N2 = np.sum(y_b, axis=1) - return ttest_ind_from_stats( - mu1, std1, N1, mu2, std2, N2, equal_var=False, alternative="two-sided" - )[0] + return ttest_ind_from_stats(mu1, + std1, + N1, + mu2, + std2, + N2, + equal_var=False, + alternative="two-sided")[0] def ttest_hist_xy(x_a, y_a, x_b, y_b, num_orders): @@ -119,8 +124,10 @@ def ttest_hist_xy(x_a, y_a, x_b, y_b, num_orders): # Take the power and fill in the values. tmp_a = np.power(tmp_a, i_order + 1) tmp_b = np.power(tmp_b, i_order + 1) - x_a_ord[i_order * num_samples: (i_order + 1) * num_samples, :] = tmp_a - x_b_ord[i_order * num_samples: (i_order + 1) * num_samples, :] = tmp_b + x_a_ord[i_order * num_samples:(i_order + 1) * + num_samples, :] = tmp_a + x_b_ord[i_order * num_samples:(i_order + 1) * + num_samples, :] = tmp_b # Compute Welch's t-test for all requested orders. ttest = ttest1_hist_xy(x_a_ord, y_a_ord, x_b_ord, y_b_ord) diff --git a/util/vendor/lowrisc_opentitan.lock.hjson b/util/vendor/lowrisc_opentitan.lock.hjson index d7cf6189..1771c84d 100644 --- a/util/vendor/lowrisc_opentitan.lock.hjson +++ b/util/vendor/lowrisc_opentitan.lock.hjson @@ -1,4 +1,4 @@ -// Copyright lowRISC contributors. +// Copyright lowRISC contributors (OpenTitan project). // Licensed under the Apache License, Version 2.0, see LICENSE for details. // SPDX-License-Identifier: Apache-2.0 @@ -9,6 +9,6 @@ upstream: { url: https://github.com/lowRISC/opentitan - rev: 8e0201f02d54d4edddb9d803cf431e71823ea7ee + rev: e65bea6b4e1d8c797ea89212f314987053a0dad4 } } diff --git a/util/vendor/lowrisc_opentitan/flake8 b/util/vendor/lowrisc_opentitan/flake8 index db48ed95..ee1b70ac 100644 --- a/util/vendor/lowrisc_opentitan/flake8 +++ b/util/vendor/lowrisc_opentitan/flake8 @@ -6,5 +6,7 @@ ignore = # the '=' sign in keyword arguments. Unfortunately, yapf inserts # them when there's a long argument (help text, for example). E251, - # Don't complain about line breaks after operators - W504 + # Don't complain about line breaks before or after operators. The + # recommendations on this have changed in 2016, so leave out the explicit + # checks: https://www.flake8rules.com/rules/W503.html + W503, W504 diff --git a/util/vendor/lowrisc_opentitan/lintpy.py b/util/vendor/lowrisc_opentitan/lintpy.py index dc4c9c05..8d8539f0 100755 --- a/util/vendor/lowrisc_opentitan/lintpy.py +++ b/util/vendor/lowrisc_opentitan/lintpy.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright lowRISC contributors. +# Copyright lowRISC contributors (OpenTitan project). # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 """Lint Python for lowRISC rules""" @@ -9,7 +9,8 @@ import subprocess import sys -import pkg_resources +from typing import List +import importlib.metadata # A map from tool name to the tuple (check, fix). These are two commands which # should be run to check for and fix errors, respectively. If the tool doesn't @@ -22,7 +23,7 @@ # include here because in hook case don't want to import reggen -def show_and_exit(clitool, packages): +def show_and_exit(clitool: str, packages: List[str]) -> None: util_path = os.path.dirname(os.path.realpath(clitool)) os.chdir(util_path) ver = subprocess.check_output( @@ -32,7 +33,7 @@ def show_and_exit(clitool, packages): ver = 'not found (not in Git repository?)' sys.stderr.write(clitool + " Git version " + ver + '\n') for p in packages: - sys.stderr.write(p + ' ' + pkg_resources.require(p)[0].version + '\n') + sys.stderr.write(p + ' ' + importlib.metadata.version(p) + '\n') sys.exit(0)