diff --git a/cw/simple_capture_aes_sca.py b/cw/simple_capture_aes_sca.py index 48338f200..101883f0b 100755 --- a/cw/simple_capture_aes_sca.py +++ b/cw/simple_capture_aes_sca.py @@ -18,6 +18,7 @@ from tqdm import tqdm from util import device, plot, trace_util +from waverunner import WaveRunner def abort_handler_during_loop(project, sig, frame): @@ -34,28 +35,19 @@ def abort_handler_during_loop(project, sig, frame): with open('simple_capture_aes_sca.yaml') as f: cfg = yaml.load(f, Loader=yaml.FullLoader) - # Key and plaintext generation building blocks - # Generate key at random based on test_random_seed. TODO: not used atm - random.seed(cfg["test"]["test_random_seed"]) - key = bytearray(cfg["test"]["key_len_bytes"]) - for i in range(0, cfg["test"]["key_len_bytes"]): - key[i] = random.randint(0, 255) - # Load initial values for key and text from cfg - key = bytearray(cfg["test"]["key"]) - print(f'Using key: {binascii.b2a_hex(bytes(key))}') - text = bytearray(cfg["test"]["text"]) - # Generating new texts/keys by encrypting using key_for_generation - key_for_gen = bytearray(cfg["test"]["key_for_gen"]) - cipher_gen = AES.new(bytes(key_for_gen), AES.MODE_ECB) - - # Cipher to generate expected responses - cipher = AES.new(bytes(key), AES.MODE_ECB) + # Choose scope from configuration + if cfg["capture"]["scope_select"] == "waverunner": + print("Using Waverunner scope") + USE_WAVERUNNER = True + USE_HUSKY = False + elif cfg["capture"]["scope_select"] == "husky": + print("Using Husky scope") + USE_WAVERUNNER = False + USE_HUSKY = True # Create OpenTitan encapsulating ChipWhisperer Husky and FPGA - # NOTE: Johann tried to split them up into classes, - # BUT scope needs FPGA (PLL?) to be configured - # and target constructor needs scope as input. - # A clean separation seems infeasible. + # NOTE: A clean separation of the two seems infeasible since + # scope needs FPGA (PLL?) to be configured and target constructor needs scope as input. cwfpgahusky = device.OpenTitan(cfg["cwfpgahusky"]["fpga_bitstream"], cfg["cwfpgahusky"]["force_program_bitstream"], cfg["cwfpgahusky"]["fw_bin"], @@ -66,61 +58,120 @@ def abort_handler_during_loop(project, sig, frame): cfg["cwfpgahusky"]["offset"], cfg["cwfpgahusky"]["output_len_bytes"]) + if USE_WAVERUNNER: + # Create WaveRunner + waverunner = WaveRunner(cfg["waverunner"]["waverunner_ip"]) + # Capture configuration: num_segments, sparsing, num_samples, first_point, acqu_channel + waverunner.configure_waveform_transfer_general(cfg["waverunner"]["num_segments"], + 1, + cfg["waverunner"]["num_samples"], + cfg["waverunner"]["sample_offset"], + "C1") + # We assume manual setup of channels, gain etc. (e.g. run this script modify while running) + # Save setup to timestamped file for reference + now = datetime.now() + now_str = now.strftime("%Y-%m-%d_%H:%M:%S") + project_name = cfg["capture"]["project_name"] + file_name_local = f"{project_name}_data/scope_config_{now_str}.lss" + waverunner.save_setup_to_local_file(file_name_local) + + # Check if batch mode is requested TODO: not supported yet + if cfg["waverunner"]["num_segments"] > 1: + print("Warning: Sequence (batch) mode not supported yet") + if cfg["cwfpgahusky"]["num_segments"] > 1: + print("Warning: Sequence (batch) mode not supported yet") + # Create ChipWhisperer project for storage of traces and metadata project = cw.create_project(cfg["capture"]["project_name"], overwrite=True) # Register ctrl-c handler to store traces on abort signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project)) + # Preparation of Key and plaintext generation + # Generate key at random based on test_random_seed (not used atm) + random.seed(cfg["test"]["test_random_seed"]) + key = bytearray(cfg["test"]["key_len_bytes"]) + for i in range(0, cfg["test"]["key_len_bytes"]): + key[i] = random.randint(0, 255) + # Load initial key and text values from cfg + key = bytearray(cfg["test"]["key"]) + print(f'Using key: {binascii.b2a_hex(bytes(key))}') + text = bytearray(cfg["test"]["text"]) + # Prepare generation of new texts/keys by encryption using key_for_generation + key_for_gen = bytearray(cfg["test"]["key_for_gen"]) + cipher_gen = AES.new(bytes(key_for_gen), AES.MODE_ECB) + # Set key cwfpgahusky.target.simpleserial_write("k", key) - # TODO: Alternative line: cwfpgahusky.target.set_key(key, ack=False) + + # Cipher to compute expected responses + cipher = AES.new(bytes(key), AES.MODE_ECB) # Main loop for measurements with progress bar for _ in tqdm(range(cfg["capture"]["num_traces"]), desc='Capturing', ncols=80): - # Generate and load new text + # Generate and load new text for this iteration text = bytearray(cipher_gen.encrypt(text)) cwfpgahusky.target.simpleserial_write('p', text) # TODO: Useful code line for batch capture # cwfpgahusky..simpleserial_write("s", capture_cfg["batch_prng_seed"].to_bytes(4, "little")) - # Arm scope - cwfpgahusky.scope.arm() - - # Capture trace - ret = cwfpgahusky.scope.capture(poll_done=False) - i = 0 - while not cwfpgahusky.target.is_done(): - i += 1 - time.sleep(0.05) - if i > 100: - print("Warning: Target did not finish operation") - if ret: - print("Warning: Timeout happened during capture") - - # Get response and verify + if USE_HUSKY: + # Arm Husky scope + cwfpgahusky.scope.arm() + + # Capture Husky trace + ret = cwfpgahusky.scope.capture(poll_done=False) + i = 0 + while not cwfpgahusky.target.is_done(): + i += 1 + time.sleep(0.05) + if i > 100: + print("Warning: Target did not finish operation") + if ret: + print("Warning: Timeout happened during capture") + + # Get Husky trace + wave = cwfpgahusky.scope.get_last_trace(as_int=True) + + if USE_WAVERUNNER: + # Arm Waverunner scope + waverunner.arm() + + # Capture and get Waverunner trace + waves = waverunner.capture_and_transfer_waves() + assert waves.shape[0] == cfg["waverunner"]["num_segments"] + # For single capture, 1st dim contains wave data + wave = waves[0,:] + # Put into uint8 range + wave = wave + 128 + + # Get response from device and verify response = cwfpgahusky.target.simpleserial_read('r', cwfpgahusky.target.output_len, ack=False) if binascii.b2a_hex(response) != binascii.b2a_hex(cipher.encrypt(bytes(text))): raise RuntimeError(f'Bad ciphertext: {response} != {cipher.encrypt(bytes(text))}.') - # Get trace - wave = cwfpgahusky.scope.get_last_trace(as_int=True) + # TODO: Useful code line for batch capture + # waves = scope.capture_and_transfer_waves() + + # Sanity check retrieved data (wave) and create CW Trace if len(wave) >= 1: trace = cw.Trace(wave, text, response, key) else: raise RuntimeError('Capture failed.') - # TODO: Useful code line for batch capture - # waves = scope.capture_and_transfer_waves() - - # Check if ADC range has been exceeded - trace_util.check_range(trace.wave, cwfpgahusky.scope.adc.bits_per_sample) + if USE_HUSKY: + # Check if ADC range has been exceeded for Husky. + # Not done for WaveRunner because clipping can be inspected on screen. + trace_util.check_range(trace.wave, cwfpgahusky.scope.adc.bits_per_sample) - # Append traces to storage - project.traces.append(trace, dtype=np.uint16) + # Append CW trace to CW project storage + if USE_HUSKY: + project.traces.append(trace, dtype=np.uint16) + if USE_WAVERUNNER: + project.traces.append(trace, dtype=np.uint8) # Save metadata and entire configuration cfg to project file project.settingsDict['datetime'] = datetime.now().strftime("%m/%d/%Y, %H:%M:%S") diff --git a/cw/simple_capture_aes_sca.yaml b/cw/simple_capture_aes_sca.yaml index 89279adbb..9b0dbf1fc 100644 --- a/cw/simple_capture_aes_sca.yaml +++ b/cw/simple_capture_aes_sca.yaml @@ -5,19 +5,26 @@ cwfpgahusky: baudrate: 115200 output_len_bytes: 16 pll_frequency: 100000000 + num_segments: 1 num_samples: 1200 offset: -40 scope_gain: 31.5 +waverunner: + waverunner_ip: 100.107.71.10 + num_segments: 1 + num_samples: 2000 + sample_offset: 0 +capture: + scope_select: husky +# scope_select: waverunner + num_traces: 50 + project_name: "projects/simple_capture_aes_sca" + show_plot: True + plot_traces: 50 + trace_image_filename: "projects/simple_capture_aes_sca_sample_traces.html" test: key_len_bytes: 16 test_random_seed: 0 key: [0x81, 0x1E, 0x37, 0x31, 0xB0, 0x12, 0x0A, 0x78, 0x42, 0x78, 0x1E, 0x22, 0xB2, 0x5C, 0xDD, 0xF9] text: [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA] key_for_gen: [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF1, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xE0, 0xF0] -capture: - num_traces: 1000 - project_name: "projects/simple_capture_aes_sca" - show_plot: True - plot_traces: 100 - trace_image_filename: "projects/simple_capture_aes_sca_sample_traces.html" - diff --git a/cw/waverunner.py b/cw/waverunner.py index 8bb502462..f0d3d88b3 100755 --- a/cw/waverunner.py +++ b/cw/waverunner.py @@ -312,6 +312,7 @@ def configure_waveform_transfer_general(self, self.num_samples = num_samples self.num_segments = num_segments self.acqu_channel = acqu_channel + # Commands below configure all except sequence mode and num_segments which is done in arm() commands = [ # WAVEFORM_SETUP # SP: sparsing, e.g. 10 for every 10th point, 1 for every point. @@ -342,14 +343,21 @@ def configure(self): self._configure_waveform_transfer() def arm(self): - """Arms the oscilloscope in sequence mode for selected channel.""" - # SEQ SEQUENCE Mode - # TRMD Trigger Mode Single - commands = [ - f"SEQ ON,{self.num_segments}", - "TRMD SINGLE", - "*OPC?", - ] + """Arms the oscilloscope for selected channel, in sequence mode if num_segments > 1.""" + if self.num_segments > 1: + # SEQ SEQUENCE Mode + # TRMD Trigger Mode Single + commands = [ + f"SEQ ON,{self.num_segments}", + "TRMD SINGLE", + "*OPC?", + ] + else: + # TRMD Trigger Mode Single + commands = [ + "TRMD SINGLE", + "*OPC?", + ] res = self._ask(";".join(commands)) assert res == "*OPC 1" @@ -369,7 +377,8 @@ def capture_and_transfer_waves(self): """ # Don't process commands until the acqu is complete and wait until # processing is complete. - res = self._ask("WAIT 10;*OPC?") + # Previously contained WAIT but did not work properly res = self._ask("WAIT 10;*OPC?") + res = self._ask("*OPC?") assert res == "*OPC 1" # Transfer and parse waveform data. if self.acqu_channel == "C1":