Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[capture] Added WaveRunner support to simple capture script and cfg file #180

Merged
merged 1 commit into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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