Skip to content

Commit

Permalink
Added WaveRunner support to simple script and cfg file
Browse files Browse the repository at this point in the history
Signed-off-by: {Johann Heyszl} <[email protected]>
  • Loading branch information
johannheyszl committed Oct 11, 2023
1 parent 698b81a commit 61ab15a
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 65 deletions.
160 changes: 110 additions & 50 deletions cw/simple_capture_aes_sca.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import yaml
from Crypto.Cipher import AES
from tqdm import tqdm
from waverunner import WaveRunner

from util import device, plot, trace_util

Expand All @@ -34,28 +35,22 @@ 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)
# 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

# Cipher to generate expected responses
cipher = AES.new(bytes(key), AES.MODE_ECB)
# Create ChipWhisperer project for storage of traces and metadata
project = cw.create_project(cfg["capture"]["project_name"], overwrite=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"],
Expand All @@ -66,61 +61,126 @@ def abort_handler_during_loop(project, sig, frame):
cfg["cwfpgahusky"]["offset"],
cfg["cwfpgahusky"]["output_len_bytes"])

# Create ChipWhisperer project for storage of traces and metadata
project = cw.create_project(cfg["capture"]["project_name"], overwrite=True)
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")

# 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
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
# Note: Capture performance tested Oct. 2023:
# Using husky with 1200 samples per trace leads to 48 it/s
# Using Waverunner with 1200 - 50000 samples per trace leads to 27 it/s
# Increases to 31 it/s when 'Performance' set to 'Analysis' in Utilies->Preferences
# Transfer over UART only slows down if .e.g transfering key 5 additional times

if USE_HUSKY:
# Arm Husky scope
cwfpgahusky.scope.arm()

if USE_WAVERUNNER:
# Arm Waverunner scope
waverunner.arm()

# Generate new text for this iteration
text = bytearray(cipher_gen.encrypt(text))
# Load text and trigger execution
cwfpgahusky.target.simpleserial_write('p', text)

if USE_HUSKY:
# 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:
# 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")
Expand Down
21 changes: 14 additions & 7 deletions cw/simple_capture_aes_sca.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 6000
sample_offset: 0
capture:
scope_select: husky
# scope_select: waverunner
num_traces: 1000
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"

24 changes: 16 additions & 8 deletions cw/waverunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"

Expand Down

0 comments on commit 61ab15a

Please sign in to comment.