diff --git a/.github/workflows/fpga.yml b/.github/workflows/fpga.yml index 9d69b88d..f9b37add 100644 --- a/.github/workflows/fpga.yml +++ b/.github/workflows/fpga.yml @@ -148,6 +148,65 @@ jobs: name: traces_sha3_random_cw310_ujson path: ./ci/projects/sha3_sca_random_cw310_ujson.html + otbn_sca_capture_cw310: + name: Capture OTBN SCA traces (CW310) + runs-on: [ubuntu-22.04-fpga, cw310] + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v4 + with: + lfs: true + + - name: Install python dependencies + run: | + python3 -m pip install --user -r python-requirements.txt + mkdir -p ci/projects + + - name: Capture OTBN Vertical Keygen traces (simpleserial) + working-directory: ci + run: | + ../capture/capture_otbn.py -c cfg/ci_otbn_sca_vertical_keygen_cw310_simpleserial.yaml -p projects/otbn_sca_vertical_keygen_cw310_simpleserial + + - name: Upload OTBN Vertical Keygen traces (simpleserial) + uses: actions/upload-artifact@v4 + with: + name: traces_otbn_sca_vertical_keygen_cw310_simpleserial + path: ./ci/projects/otbn_sca_vertical_keygen_cw310_simpleserial.html + + - name: Capture OTBN Vertical Keygen traces (ujson) + working-directory: ci + run: | + ../capture/capture_otbn.py -c cfg/ci_otbn_sca_vertical_keygen_cw310_ujson.yaml -p projects/otbn_sca_vertical_keygen_cw310_ujson + + - name: Upload OTBN Vertical Keygen traces (ujson) + uses: actions/upload-artifact@v4 + with: + name: traces_otbn_sca_vertical_keygen_cw310_ujson + path: ./ci/projects/otbn_sca_vertical_keygen_cw310_ujson.html + + - name: Capture OTBN Vertical Modinv traces (simpleserial) + working-directory: ci + run: | + ../capture/capture_otbn.py -c cfg/ci_otbn_sca_vertical_modinv_cw310_simpleserial.yaml -p projects/otbn_sca_vertical_modinv_cw310_simpleserial + + - name: Upload OTBN Vertical Modinv traces (simpleserial) + uses: actions/upload-artifact@v4 + with: + name: traces_otbn_sca_vertical_modinv_cw310_simpleserial + path: ./ci/projects/otbn_sca_vertical_modinv_cw310_simpleserial.html + + - name: Capture OTBN Vertical Modinv traces (ujson) + working-directory: ci + run: | + ../capture/capture_otbn.py -c cfg/ci_otbn_sca_vertical_modinv_cw310_ujson.yaml -p projects/otbn_sca_vertical_modinv_cw310_ujson + + - name: Upload OTBN Vertical Modinv traces (ujson) + uses: actions/upload-artifact@v4 + with: + name: traces_otbn_sca_vertical_modinv_cw310_ujson + path: ./ci/projects/otbn_sca_vertical_modinv_cw310_ujson.html + sca_capture_cw305: name: Capture AES SCA traces (CW305) runs-on: [ubuntu-22.04-fpga, cw305] diff --git a/capture/capture_otbn.py b/capture/capture_otbn.py index c399d498..586fe8a4 100755 --- a/capture/capture_otbn.py +++ b/capture/capture_otbn.py @@ -21,7 +21,8 @@ from tqdm import tqdm import util.helpers as helpers -from target.communication.sca_otbn_commands import OTOTBNVERT +from target.communication.sca_otbn_commands import OTOTBN +from target.communication.sca_prng_commands import OTPRNG from target.communication.sca_trigger_commands import OTTRIGGER from target.targets import Target, TargetConfig from util import check_version @@ -64,12 +65,10 @@ class CaptureConfig: num_traces: int num_segments: int output_len: int - text_fixed: bytearray - key_fixed: bytearray key_len_bytes: int text_len_bytes: int - C: Optional[bytearray] - seed_fixed: Optional[bytearray] + C: Optional[list] + seed_fixed: Optional[list] expected_fixed_key: Optional[bytearray] k_fixed: Optional[bytearray] expected_fixed_output: Optional[int] @@ -101,8 +100,7 @@ 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']} ...") @@ -123,59 +121,42 @@ def setup(cfg: dict, project: Path): # Init scope. scope_type = cfg["capture"]["scope_select"] - # Determine sampling rate, if necessary. + # Will determine sampling rate (for Husky only), if not given in cfg. cfg[scope_type]["sampling_rate"] = determine_sampling_rate(cfg, scope_type) - # Convert number of cycles into number of samples, if necessary. + # 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) - # Convert offset in cycles into offset in samples, if necessary. + # 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) - 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 = cfg["test"]["batch_mode"] # Create scope config & setup scope. scope_cfg = ScopeConfig( - scope_type=scope_type, + scope_type = scope_type, batch_mode = batch, bit = cfg[scope_type].get("bit"), - acqu_channel=cfg[scope_type].get("channel"), - ip=cfg[scope_type].get("waverunner_ip"), - num_samples=cfg[scope_type]["num_samples"], - offset_samples=cfg[scope_type]["offset_samples"], - sampling_rate=cfg[scope_type].get("sampling_rate"), - num_segments=cfg[scope_type]["num_segments"], - sparsing=cfg[scope_type].get("sparsing"), - scope_gain=cfg[scope_type].get("scope_gain"), - pll_frequency=cfg["target"]["pll_frequency"], + acqu_channel = cfg[scope_type].get("channel"), + ip = cfg[scope_type].get("waverunner_ip"), + num_samples = cfg[scope_type]["num_samples"], + offset_samples = cfg[scope_type]["offset_samples"], + sampling_rate = cfg[scope_type].get("sampling_rate"), + num_segments = cfg[scope_type].get("num_segments"), + sparsing = cfg[scope_type].get("sparsing"), + scope_gain = cfg[scope_type].get("scope_gain"), + pll_frequency = cfg["target"]["pll_frequency"], ) scope = Scope(scope_cfg) - # OTBN's public-key operations might not fit into the sample buffer of the scope - # These two parameters allows users to conrol the sampling frequency - # `adc_mul` affects the sample frequency (clock_freq = adc_mul * pll_freq) - # `decimate` is the ADC downsampling factor that allows us to sample at - # every `decimate` cycles. - if scope_type == "husky": - if "adc_mul" in cfg["husky"]: - scope.scope.scope.clock.adc_mul = cfg["husky"]["adc_mul"] - if "decimate" in cfg["husky"]: - scope.scope.scope.adc.decimate = cfg["husky"]["decimate"] - # Print final scope parameter - logger.info( - f'Scope setup with final sampling rate of {scope.scope.scope.clock.adc_freq} S/s' - ) - # Init project. - project_cfg = ProjectConfig( - type=cfg["capture"]["trace_db"], - path=project, - wave_dtype=np.uint16, - overwrite=True, - trace_threshold=cfg["capture"].get("trace_threshold")) + project_cfg = ProjectConfig(type = cfg["capture"]["trace_db"], + path = project, + wave_dtype = np.uint16, + overwrite = True, + trace_threshold = cfg["capture"].get("trace_threshold") + ) project = SCAProject(project_cfg) project.create_project() @@ -190,43 +171,41 @@ def establish_communication(target, capture_cfg: CaptureConfig): curve_cfg: The capture config Returns: - ot_otbn_vert: The communication interface to the OTBN app. + ot_otbn: The communication interface to the OTBN app. + ot_prng: The communication interface to the PRNG SCA application. ot_trig: The communication interface to the SCA trigger. """ # Create communication interface to OTBN. - ot_otbn_vert = OTOTBNVERT(target=target, protocol=capture_cfg.protocol) + ot_otbn = OTOTBN(target=target, protocol=capture_cfg.protocol) + + # Create communication interface to OT PRNG. + ot_prng = OTPRNG(target=target, protocol=capture_cfg.protocol) # Create communication interface to SCA trigger. ot_trig = OTTRIGGER(target=target, protocol=capture_cfg.protocol) - return ot_otbn_vert, ot_trig + return ot_otbn, ot_prng, ot_trig -def configure_cipher(cfg: dict, target, capture_cfg: CaptureConfig, - ot_otbn_vert) -> OTOTBNVERT: +def configure_cipher(cfg: dict, capture_cfg: CaptureConfig, ot_otbn) -> OTOTBN: """ Configure the OTBN app. Establish communication with the OTBN keygen app and configure the seed. Args: cfg: The configuration for the current experiment. - target: The OT target. curve_cfg: The curve config. capture_cfg: The configuration of the capture. - ot_otbn_vert: The communication interface to the OTBN app. + ot_otbn: The communication interface to the OTBN app. Returns: curve_cfg: The curve configuration values. """ - # Seed host's PRNG. - random.seed(cfg["test"]["batch_prng_seed"]) - - # Seed the target's PRNGs - ot_otbn_vert.write_batch_prng_seed(cfg["test"]["batch_prng_seed"].to_bytes( - 4, "little")) + # Initialize OTBN on the target. + ot_otbn.init() # select the otbn app on the device (0 -> keygen, 1 -> modinv) - ot_otbn_vert.choose_otbn_app(cfg["test"]["app"]) + ot_otbn.choose_otbn_app(cfg["test"]["app"]) # Initialize some curve-dependent parameters. if cfg["test"]["curve"] == 'p256': @@ -239,107 +218,65 @@ def configure_cipher(cfg: dict, target, capture_cfg: CaptureConfig, modinv_share_bytes=320 // 8, modinv_mask_bytes=128 // 8) else: - # Create curve config object - curve_cfg = CurveConfig(curve_order_n=0, - key_bytes=0, - seed_bytes=0, - modinv_share_bytes=0, - modinv_mask_bytes=0) # TODO: add support for P384 raise NotImplementedError( f'Curve {cfg["test"]["curve"]} is not supported') if capture_cfg.capture_mode == "keygen": - if capture_cfg.batch_mode: - # TODO: add support for batch mode - raise NotImplementedError('Batch mode not yet supported.') - else: - # Generate fixed constants for all traces of the keygen operation. - if cfg["test"]["test_type"] == 'KEY': - # In fixed-vs-random KEY mode we use two fixed constants: - # 1. C - a 320 bit constant redundancy - # 2. fixed_number - a 256 bit number used to derive the fixed key - # for the fixed set of measurements. Note that in - # this set, the fixed key is equal to - # (C + fixed_number) mod curve_order_n - r1, r2, r3 = dg.get_random() - capture_cfg.C = (bytearray(r1) + bytearray(r2) + - bytearray(r3))[:curve_cfg.seed_bytes] - r1, r2, r3 = dg.get_random() - fixed_number = (bytearray(r1) + bytearray(r2) + - bytearray(r3))[:curve_cfg.key_bytes] - 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') - 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 - # for the fixed set of measurements. Note that in - # this set, the fixed key is equal to: - # seed_fixed mod curve_order_n - r1, r2, r3 = dg.get_random() - capture_cfg.seed_fixed = (bytearray(r1) + bytearray(r2) + - bytearray(r3))[:curve_cfg.seed_bytes] - - # 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 - elif capture_cfg.capture_mode == "modinv": - if capture_cfg.batch_mode: - # TODO: add support for batch mode - raise NotImplementedError('Batch mode not supported.') + # Generate fixed constants for all traces of the keygen operation. + if cfg["test"]["test_type"] == 'KEY': + # In fixed-vs-random KEY mode we use two fixed constants: + # 1. C - a 320 bit constant redundancy + # 2. fixed_number - a 256 bit number used to derive the fixed key + # for the fixed set of measurements. Note that in + # this set, the fixed key is equal to + # (C + fixed_number) mod curve_order_n + capture_cfg.C = random.randbytes(curve_cfg.seed_bytes) + fixed_number = random.randbytes(curve_cfg.key_bytes) + 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') else: - # set fixed key and share inputs - # (uncomment the desired fixed shares depending on whether - # you want a random fixed key or a hardcoded fixed key) - # 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') - # 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) + # 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 + # for the fixed set of measurements. Note that in + # this set, the fixed key is equal to: + # seed_fixed mod curve_order_n + capture_cfg.seed_fixed = random.randbytes(curve_cfg.seed_bytes) + else: + # TODO: add support for Modinv + raise NotImplementedError( + f'Curve {cfg["test"]["app"]} is not supported') 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, curve_cfg: CurveConfig, + capture_cfg: CaptureConfig, sample_fixed: int): """ Generate cipher material for keygen application. Args: cfg: The configuration for the current experiment. - sample_fixed: Use fixed key or new key. curve_cfg: The curve config. capture_cfg: The configuration of the capture. Returns: seed_used: The next seed. mask: The next mask. - expected_key: The next expected key. - sample_fixed: Is the next sample fixed or not? + d0_expected: The expected d0 share. + sample_fixed: Fixed or random run. """ - - if capture_cfg.batch_mode: - # TODO: add support for batch mode - raise NotImplementedError('Batch mode not yet supported.') - else: - if cfg["test"]["masks_off"] == 'True': - # Use a constant mask for each trace - mask = bytearray(curve_cfg.seed_bytes) # all zeros - else: + masks = [] + seeds = [] + d0_expected = [] + mod = curve_cfg.curve_order_n << ((curve_cfg.seed_bytes - curve_cfg.key_bytes) * 8) + for i in range(capture_cfg.num_segments): + if cfg["test"]["masks_on"]: # Generate a new random mask for each trace. - r1, r2, r3 = dg.get_random() - mask = (bytearray(r1) + bytearray(r2) + - bytearray(r3))[:curve_cfg.seed_bytes] - # Generate fixed constants for all traces of the keygen operation. + masks.append(random.randbytes(curve_cfg.seed_bytes)) + else: + # Use a constant 0 mask for each trace. + masks.append(bytearray(0)) + if cfg["test"]["test_type"] == 'KEY': # In fixed-vs-random KEY mode, the fixed set of measurements is # generated using the fixed 320 bit seed. The random set of @@ -349,18 +286,11 @@ def generate_ref_crypto_keygen(cfg: dict, sample_fixed, curve_cfg: CurveConfig, # constant. Note that in this case the used key is equal to # (C + r) mod curve_order_n if sample_fixed: - seed_used = capture_cfg.seed_fixed - expected_key = capture_cfg.expected_fixed_key + seeds.append(capture_cfg.seed_fixed) else: - r1, r2, r3 = dg.get_random() - random_number = (bytearray(r1) + bytearray(r2) + - bytearray(r3))[:curve_cfg.key_bytes] - seed_used_int = int.from_bytes(capture_cfg.C, byteorder='little') + \ - int.from_bytes(random_number, byteorder='little') - seed_used = seed_used_int.to_bytes(curve_cfg.seed_bytes, - byteorder='little') - expected_key = int.from_bytes(seed_used, byteorder='little') % \ - curve_cfg.curve_order_n + random_number = random.randbytes(curve_cfg.key_bytes) + seed_used_int = int.from_bytes(capture_cfg.C, byteorder='little') + int.from_bytes(random_number, byteorder='little') + seeds.append(seed_used_int.to_bytes(curve_cfg.seed_bytes, byteorder='little')) else: # In fixed-vs-random SEED mode, the fixed set of measurements is # generated using the fixed 320 bit seed. The random set of @@ -368,303 +298,119 @@ def generate_ref_crypto_keygen(cfg: dict, sample_fixed, curve_cfg: CurveConfig, # cases, the used key is equal to: # seed mod curve_order_n if sample_fixed: - seed_used = capture_cfg.seed_fixed - expected_key = capture_cfg.expected_fixed_key + seeds.append(capture_cfg.seed_fixed) else: - r1, r2, r3 = dg.get_random() - seed_used = (bytearray(r1) + bytearray(r2) + - bytearray(r3))[:curve_cfg.seed_bytes] - expected_key = int.from_bytes(seed_used, byteorder='little') % \ - curve_cfg.curve_order_n - + seeds.append(random.randbytes(curve_cfg.seed_bytes)) + # Calculate expected d0 share. + mask_int = int.from_bytes(masks[i], "little") + seed = int.from_bytes(seeds[i], "little") ^ mask_int + d0 = ((seed ^ mask_int) - mask_int) % mod + d0_expected.append(d0.to_bytes(curve_cfg.seed_bytes, byteorder='little')) + # The next sample is either fixed or random. - r1, r2, r3 = dg.get_random() - sample_fixed = r1[0] & 0x1 + sample_fixed = random.getrandbits(32) & 0x1 - return seed_used, mask, expected_key, sample_fixed + return seeds, masks, d0_expected, sample_fixed -def generate_ref_crypto_modinv(cfg: dict, sample_fixed, curve_cfg: CurveConfig, - capture_cfg: CaptureConfig): - """ Generate cipher material for the modular inverse operation. +def check_d0_keygen(ot_otbn: OTOTBN, d0_expected: list, curve_cfg: CurveConfig): + """ Compares the received d0 with the generated d0 share. Args: - cfg: The configuration for the current experiment. - sample_fixed: Use fixed key or new key. + ot_otbn: The OpenTitan OTBN vertical communication interface. + d0_expected: The pre-computed key. curve_cfg: The curve config. - capture_cfg: The configuration of the capture. - - Returns: - k_used: The next scalar value. - input_k0_used: The next first share of k_used. - input_k1_used: The next second share of k_used. - expected_output: The next expected modinv output. - sample_fixed: Is the next sample fixed or not? """ + # Read the output and compare. + d0_received = ot_otbn.read_batch_digest() - if capture_cfg.batch_mode: - # TODO: add support for batch mode - raise NotImplementedError('Batch mode not yet supported.') - else: - if sample_fixed: - # Compute the fixed input shares: - # generate two random 320-bit shares - r1, r2, r3 = dg.get_random() - input_k0_fixed = (bytearray(r1) + bytearray(r2) + - bytearray(r3))[:curve_cfg.modinv_share_bytes] - r1, r2, r3 = dg.get_random() - input_k1_fixed = (bytearray(r1) + bytearray(r2) + - bytearray(r3))[:curve_cfg.modinv_share_bytes] - k0_fixed = int.from_bytes(input_k0_fixed, byteorder='little') - k1_fixed = int.from_bytes(input_k1_fixed, byteorder='little') - # 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 - 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')) - # Use the fixed input. - input_k0_used = input_k0_fixed - input_k1_used = input_k1_fixed - k_used = capture_cfg.k_fixed - expected_output = capture_cfg.expected_fixed_output - else: - # Use a random input. - r1, r2, r3 = dg.get_random() - input_k0_used = (bytearray(r1) + bytearray(r2) + - bytearray(r3))[:curve_cfg.modinv_share_bytes] - r1, r2, r3 = dg.get_random() - 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')) - expected_output = pow(k_used_int, -1, curve_cfg.curve_order_n) - - # The next sample is either fixed or random. - r1, r2, r3 = dg.get_random() - sample_fixed = r1[0] & 0x1 - - return k_used, input_k0_used, input_k1_used, expected_output, sample_fixed - - -def check_ciphertext_keygen(ot_otbn_vert: OTOTBNVERT, 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 - generated key. In batch mode, only the last key is compared. - Asserts on mismatch. - - Args: - ot_otbn_vert: The OpenTitan OTBN vertical communication interface. - expected_key: The pre-computed key. - curve_cfg: The curve config. - - Returns: - share0: First share of the received key. - share1: Second share of the received key. - """ - # Read the output, unmask the key, and check if it matches - # expectations. - share0 = ot_otbn_vert.read_output(curve_cfg.seed_bytes) - share1 = ot_otbn_vert.read_output(curve_cfg.seed_bytes) - if share0 is None: - raise RuntimeError('Random share0 is none') - if share1 is None: - raise RuntimeError('Random share1 is none') - - d0 = int.from_bytes(share0, byteorder='little') - d1 = int.from_bytes(share1, byteorder='little') - actual_key = (d0 + d1) % curve_cfg.curve_order_n + # Calculate XOR of all expected d0. + d0_exp_batch_digest = None + for d0 in d0_expected: + d0_int = int.from_bytes(d0, "little") + d0_exp_batch_digest = (d0_int if d0_exp_batch_digest is None else d0_int ^ d0_exp_batch_digest) + d0_expected = bytearray(d0_exp_batch_digest.to_bytes(curve_cfg.seed_bytes, byteorder='little')) + d0_expected = [x for x in d0_expected] - assert actual_key == expected_key, (f"Incorrect encryption result!\n" - f"actual: {actual_key}\n" - f"expected: {expected_key}") + assert d0_expected == d0_received, (f"Incorrect d0 result!\n" + f"actual: {d0_received}\n" + f"expected: {d0_expected}") - return share0, share1 - -def check_ciphertext_modinv(ot_otbn_vert: OTOTBNVERT, expected_output, - curve_cfg: CurveConfig): - """ Compares the received modular inverse output with the generated output. - - Args: - ot_otbn_vert: The OpenTitan OTBN vertical communication interface. - expected_key: The pre-computed key. - curve_cfg: The curve config. - - Returns: - actual_output: The received output of the modinv operation. - """ - # Read the output, unmask it, and check if it matches expectations. - kalpha_inv = ot_otbn_vert.read_output(curve_cfg.key_bytes) - alpha = ot_otbn_vert.read_output(curve_cfg.modinv_mask_bytes) - if kalpha_inv is None: - raise RuntimeError('kaplpha_inv is none') - if alpha is None: - 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 - - assert actual_output == expected_output, (f"Incorrect modinv result!\n" - f"actual: {actual_output}\n" - f"expected: {expected_output}") - - return actual_output - - -def capture_keygen(cfg: dict, scope: Scope, ot_otbn_vert: OTOTBNVERT, +def capture_keygen(cfg: dict, scope: Scope, ot_otbn: OTOTBN, capture_cfg: CaptureConfig, curve_cfg: CurveConfig, - project: SCAProject, target: Target): + project: SCAProject, target: Target, ot_prng: OTPRNG): """ Capture power consumption during selected OTBN operation. Args: cfg: The configuration for the current experiment. scope: The scope class representing a scope (Husky or WaveRunner). - ot_otbn_vert: The OpenTitan OTBN vertical communication interface. + ot_otbn: The OpenTitan OTBN vertical communication interface. capture_cfg: The configuration of the capture. curve_cfg: The curve config. project: The SCA project. target: The OpenTitan target. + ot_prng: The interface to the OpenTitan PRNG. """ - # Initial seed. - seed_used = capture_cfg.seed_fixed - # Initial mask. - mask = bytearray(curve_cfg.seed_bytes) # all zeros - # Initial expected key. - expected_key = capture_cfg.expected_fixed_key - - # FVSR setup. + # Seed host's PRNG. + random.seed(cfg["test"]["batch_prng_seed"]) + + # Seed the target's PRNGs + ot_prng.seed_prng(cfg["test"]["batch_prng_seed"].to_bytes(4, "little")) + + # First sample is always fixed. sample_fixed = 1 # Optimization for CW trace library. num_segments_storage = 1 + # Enable or disable masking. + #ot_otbn.config_keygen_masking(cfg["test"]["masks_on"]) + capture_cfg.batch_mode = True + # 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. scope.arm() - - seed_used, mask, expected_key, sample_fixed = generate_ref_crypto_keygen( - cfg, sample_fixed, curve_cfg, capture_cfg) - + + # Generate reference crypto material. + seeds, masks, expected_d0, sample_fixed = generate_ref_crypto_keygen(cfg, + curve_cfg, + capture_cfg, + sample_fixed) # Trigger encryption. if capture_cfg.batch_mode: - # TODO: add support for batch mode - raise NotImplementedError('Batch mode not yet supported.') - else: # Send the seed to ibex. # Ibex receives the seed and the mask and computes the two shares as: # Share0 = seed XOR mask # Share1 = mask # These shares are then forwarded to OTBN. - ot_otbn_vert.write_keygen_seed(seed_used) - ot_otbn_vert.start_keygen(mask) - - # Capture traces. - waves = scope.capture_and_transfer_waves(target) - assert waves.shape[0] == capture_cfg.num_segments - - # Compare received key with generated key. - share0, share1 = check_ciphertext_keygen( - ot_otbn_vert, expected_key, curve_cfg) - - # Store trace into database. - project.append_trace(wave=waves[0, :], - plaintext=mask, - ciphertext=share0 + share1, - key=seed_used) - - # Memory allocation optimization for CW trace library. - num_segments_storage = project.optimize_capture( - num_segments_storage) - - # Update the loop variable and the progress bar. - remaining_num_traces -= capture_cfg.num_segments - pbar.update(capture_cfg.num_segments) - - -def capture_modinv(cfg: dict, scope: Scope, ot_otbn_vert: OTOTBNVERT, - capture_cfg: CaptureConfig, curve_cfg: CurveConfig, - project: SCAProject, target: Target): - """ Capture power consumption during selected OTBN operation. - - Args: - cfg: The configuration for the current experiment. - scope: The scope class representing a scope (Husky or WaveRunner). - ot_otbn_vert: The OpenTitan OTBN vertical communication interface. - capture_cfg: The configuration of the capture. - curve_cfg: The curve config. - project: The SCA project. - target: The OpenTitan target. - """ - # Initial scalar k. - k_used = capture_cfg.k_fixed - # Initial expected key. - expected_output = capture_cfg.expected_fixed_output - - # FVSR setup. - sample_fixed = 1 - - # Optimization for CW trace library. - num_segments_storage = 1 - - # 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: - while remaining_num_traces > 0: - # Arm the scope. - 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) - - # Trigger encryption. - if capture_cfg.batch_mode: - # TODO: add support for batch mode - raise NotImplementedError('Batch mode not supported.') + if cfg["test"]["test_type"] == 'KEY': + ot_otbn.write_keygen_key_constant_redundancy(capture_cfg.C) + ot_otbn.write_keygen_seed(capture_cfg.seed_fixed) + ot_otbn.start_keygen_batch(cfg["test"]["test_type"], capture_cfg.num_segments) else: - # Start modinv device computation - ot_otbn_vert.start_modinv(input_k0_used, input_k1_used) - - # Capture traces. - waves = scope.capture_and_transfer_waves(target) - assert waves.shape[0] == capture_cfg.num_segments - - # Compare received key with generated key. - actual_output = check_ciphertext_modinv( - ot_otbn_vert, expected_output, curve_cfg) - - # Store trace into database. - project.append_trace(wave=waves[0, :], - plaintext=k_used, - ciphertext=bytearray( - actual_output.to_bytes( - curve_cfg.key_bytes, 'little')), - key=k_used) + # TODO: add support for batch mode + raise NotImplementedError('Non-batch mode not yet supported.') + + # Capture traces. + waves = scope.capture_and_transfer_waves(target) + assert waves.shape[0] == capture_cfg.num_segments + + # Compare received d0 with generated d0. + check_d0_keygen(ot_otbn, expected_d0, curve_cfg) + + # Store trace into database. + for i in range(capture_cfg.num_segments): + assert len(waves[i, :]) >= 1 + project.append_trace(wave = waves[i, :], + plaintext=masks[i], + ciphertext=expected_d0[i], + key=seeds[i]) # Memory allocation optimization for CW trace library. num_segments_storage = project.optimize_capture( @@ -724,11 +470,10 @@ def main(argv=None): num_traces=cfg["capture"]["num_traces"], num_segments=scope.scope_cfg.num_segments, output_len=cfg["target"]["output_len_bytes"], - text_fixed=bytearray(), - key_fixed=bytearray(), key_len_bytes=cfg["test"]["key_len_bytes"], text_len_bytes=cfg["test"]["text_len_bytes"], protocol=cfg["target"]["protocol"], + port = cfg["target"].get("port"), C=bytearray(), seed_fixed=bytearray(), expected_fixed_key=bytearray(), @@ -739,25 +484,18 @@ def main(argv=None): ) # Open communication with target. - ot_otbn_vert, ot_trig = establish_communication(target, capture_cfg) + ot_otbn, ot_prng, ot_trig = establish_communication(target, capture_cfg) # Configure cipher. - curve_cfg = configure_cipher(cfg, target, capture_cfg, ot_otbn_vert) + curve_cfg = configure_cipher(cfg, capture_cfg, ot_otbn) - # Configure trigger source. - # 0 for HW, 1 for SW. - trigger_source = 1 - if "hw" in cfg["target"].get("trigger"): - trigger_source = 0 - ot_trig.select_trigger(trigger_source) + # Select SW trigger on the device. + ot_trig.select_trigger(1) # Capture traces. if mode == "keygen": - 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_keygen(cfg, scope, ot_otbn, capture_cfg, curve_cfg, + project, target, ot_prng) else: # TODO: add support for modinv app raise NotImplementedError('Cofigured OTBN app not yet supported.') @@ -772,12 +510,12 @@ def main(argv=None): metadata["num_samples"] = scope.scope_cfg.num_samples 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["samples_trigger_high"] = scope.scope.scope.adc.trig_count - else: - metadata["sampling_rate"] = scope.scope_cfg.sampling_rate + #if cfg["capture"]["scope_select"] == "husky": + # 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 metadata["num_traces"] = capture_cfg.num_traces metadata["cfg_file"] = str(args.cfg) # Store bitstream information. @@ -794,7 +532,7 @@ def main(argv=None): # Store user provided notes. metadata["notes"] = args.notes # Store the Git hash. - metadata["git_hash"] = helpers.get_git_hash() + #metadata["git_hash"] = helpers.get_git_hash() # Write metadata into project database. project.write_metadata(metadata) diff --git a/capture/configs/otbn_ecc256_keygen_cw310.yaml b/capture/configs/otbn_ecc256_keygen_cw310.yaml new file mode 100644 index 00000000..980ac52d --- /dev/null +++ b/capture/configs/otbn_ecc256_keygen_cw310.yaml @@ -0,0 +1,46 @@ +target: + target_type: cw310 + fpga_bitstream: "../objs/lowrisc_systems_chip_earlgrey_cw310_0.1.bit" + force_program_bitstream: True + fw_bin: ../objs/sca_ujson_fpga_cw310.bin + # target_clk_mult is a hardcoded value in the bitstream. Do not change. + target_clk_mult: 0.24 + target_freq: 24000000 + baudrate: 115200 + output_len_bytes: 40 + protocol: "ujson" + port: "/dev/ttyACM1" +husky: + sampling_rate: 200000000 + num_segments: 10 + num_cycles: 1075 + offset_cycles: 0 + scope_gain: 24 + adc_mul: 1 + decimate: 1 +waverunner: + waverunner_ip: 100.107.71.10 + num_segments: 1 + num_samples: 6000 + sample_offset: 0 +capture: + scope_select: husky + show_plot: True + plot_traces: 100 + num_traces: 1000000 + trace_threshold: 110000 + trace_db: ot_trace_library +test: + batch_prng_seed: 0 + key_len_bytes: 40 + text_len_bytes: 40 + plain_text_len_bytes: 40 + masks_on: True + # Currently, 'p256' is the only supported curve. + curve: p256 + # Select the OTBN app to analyze. Currently available: 'keygen', 'modinv' + app: keygen + # For app = keygen: There are two fixed-vs-random test types, KEY and SEED + # Currently batch-mode capture only works with SEED + test_type: KEY + batch_mode: True \ No newline at end of file diff --git a/capture/configs/otbn_vertical_keygen_sca_cw310.yaml b/capture/configs/otbn_vertical_keygen_sca_cw310.yaml index 85dbe96e..24d26a1c 100644 --- a/capture/configs/otbn_vertical_keygen_sca_cw310.yaml +++ b/capture/configs/otbn_vertical_keygen_sca_cw310.yaml @@ -2,22 +2,23 @@ target: target_type: cw310 fpga_bitstream: "../objs/lowrisc_systems_chip_earlgrey_cw310_0.1.bit" force_program_bitstream: True - fw_bin: "../objs/otbn_vertical_serial_fpga_cw310.bin" + # fw_bin: "../objs/otbn_vertical_serial_fpga_cw310.bin" + fw_bin: ../objs/sca_ujson_fpga_cw310.bin # target_clk_mult is a hardcoded value in the bitstream. Do not change. target_clk_mult: 0.24 target_freq: 24000000 baudrate: 115200 output_len_bytes: 40 - protocol: "simpleserial" - # protocol: "ujson" - # port: "/dev/ttyACM4" + # protocol: "simpleserial" + protocol: "ujson" + port: "/dev/ttyACM4" # Trigger source. # hw: Precise, hardware-generated trigger - FPGA only. # sw: Fully software-controlled trigger. trigger: "hw" husky: sampling_rate: 200000000 - num_segments: 20 + num_segments: 1 num_cycles: 200 offset_cycles: 0 scope_gain: 24 @@ -25,7 +26,7 @@ husky: decimate: 1 waverunner: waverunner_ip: 100.107.71.10 - num_segments: 20 + num_segments: 1 num_samples: 6000 sample_offset: 0 capture: diff --git a/ci/cfg/ci_otbn_sca_vertical_keygen_cw310_simpleserial.yaml b/ci/cfg/ci_otbn_sca_vertical_keygen_cw310_simpleserial.yaml new file mode 100644 index 00000000..8cb5109d --- /dev/null +++ b/ci/cfg/ci_otbn_sca_vertical_keygen_cw310_simpleserial.yaml @@ -0,0 +1,44 @@ +target: + target_type: cw310 + fpga_bitstream: "../objs/lowrisc_systems_chip_earlgrey_cw310_0.1.bit" + force_program_bitstream: False + fw_bin: "../objs/otbn_vertical_serial_fpga_cw310.bin" + # target_clk_mult is a hardcoded value in the bitstream. Do not change. + target_clk_mult: 0.24 + target_freq: 24000000 + baudrate: 115200 + output_len_bytes: 40 + protocol: "simpleserial" + # Trigger source. + # hw: Precise, hardware-generated trigger - FPGA only. + # sw: Fully software-controlled trigger. + trigger: "hw" +husky: + sampling_rate: 200000000 + num_segments: 1 + num_cycles: 200 + offset_cycles: 0 + scope_gain: 24 + adc_mul: 1 + decimate: 1 +capture: + scope_select: husky + show_plot: True + plot_traces: 10 + num_traces: 100 + trace_threshold: 10000 + trace_db: ot_trace_library +test: + batch_prng_seed: 6 + key_len_bytes: 40 + text_len_bytes: 40 + plain_text_len_bytes: 40 + masks_off: False + # Currently, 'p256' is the only supported curve. + curve: p256 + # Select the OTBN app to analyze. Currently available: 'keygen', 'modinv' + app: keygen + # For app = keygen: There are two fixed-vs-random test types, KEY and SEED + # Currently batch-mode capture only works with SEED + test_type: SEED + batch_mode: False \ No newline at end of file diff --git a/ci/cfg/ci_otbn_sca_vertical_keygen_cw310_ujson.yaml b/ci/cfg/ci_otbn_sca_vertical_keygen_cw310_ujson.yaml new file mode 100644 index 00000000..97259f7c --- /dev/null +++ b/ci/cfg/ci_otbn_sca_vertical_keygen_cw310_ujson.yaml @@ -0,0 +1,45 @@ +target: + target_type: cw310 + fpga_bitstream: "../objs/lowrisc_systems_chip_earlgrey_cw310_0.1.bit" + force_program_bitstream: False + fw_bin: ../objs/sca_ujson_fpga_cw310.bin + # target_clk_mult is a hardcoded value in the bitstream. Do not change. + target_clk_mult: 0.24 + target_freq: 24000000 + baudrate: 115200 + output_len_bytes: 40 + protocol: "ujson" + port: "/dev/ttyACM_CW310_1" + # Trigger source. + # hw: Precise, hardware-generated trigger - FPGA only. + # sw: Fully software-controlled trigger. + trigger: "hw" +husky: + sampling_rate: 200000000 + num_segments: 1 + num_cycles: 200 + offset_cycles: 0 + scope_gain: 24 + adc_mul: 1 + decimate: 1 +capture: + scope_select: husky + show_plot: True + plot_traces: 10 + num_traces: 100 + trace_threshold: 10000 + trace_db: ot_trace_library +test: + batch_prng_seed: 6 + key_len_bytes: 40 + text_len_bytes: 40 + plain_text_len_bytes: 40 + masks_off: False + # Currently, 'p256' is the only supported curve. + curve: p256 + # Select the OTBN app to analyze. Currently available: 'keygen', 'modinv' + app: keygen + # For app = keygen: There are two fixed-vs-random test types, KEY and SEED + # Currently batch-mode capture only works with SEED + test_type: SEED + batch_mode: False \ No newline at end of file diff --git a/capture/configs/otbn_vertical_modinv_sca_cw310.yaml b/ci/cfg/ci_otbn_sca_vertical_modinv_cw310_simpleserial.yaml similarity index 83% rename from capture/configs/otbn_vertical_modinv_sca_cw310.yaml rename to ci/cfg/ci_otbn_sca_vertical_modinv_cw310_simpleserial.yaml index 2a54bc51..5ff93281 100644 --- a/capture/configs/otbn_vertical_modinv_sca_cw310.yaml +++ b/ci/cfg/ci_otbn_sca_vertical_modinv_cw310_simpleserial.yaml @@ -1,7 +1,7 @@ target: target_type: cw310 fpga_bitstream: "../objs/lowrisc_systems_chip_earlgrey_cw310_0.1.bit" - force_program_bitstream: True + force_program_bitstream: False fw_bin: "../objs/otbn_vertical_serial_fpga_cw310.bin" # target_clk_mult is a hardcoded value in the bitstream. Do not change. target_clk_mult: 0.24 @@ -9,12 +9,10 @@ target: baudrate: 115200 output_len_bytes: 40 protocol: "simpleserial" - # protocol: "ujson" - # port: "/dev/ttyACM4" # Trigger source. # hw: Precise, hardware-generated trigger - FPGA only. # sw: Fully software-controlled trigger. - trigger: "hw" + trigger: "sw" husky: sampling_rate: 200000000 num_segments: 20 @@ -23,16 +21,11 @@ husky: scope_gain: 24 adc_mul: 1 decimate: 1 -waverunner: - waverunner_ip: 100.107.71.10 - num_segments: 20 - num_samples: 6000 - sample_offset: 0 capture: scope_select: husky show_plot: True - plot_traces: 100 - num_traces: 1000 + plot_traces: 10 + num_traces: 100 trace_threshold: 10000 trace_db: ot_trace_library test: diff --git a/ci/cfg/ci_otbn_sca_vertical_modinv_cw310_ujson.yaml b/ci/cfg/ci_otbn_sca_vertical_modinv_cw310_ujson.yaml new file mode 100644 index 00000000..289207c5 --- /dev/null +++ b/ci/cfg/ci_otbn_sca_vertical_modinv_cw310_ujson.yaml @@ -0,0 +1,45 @@ +target: + target_type: cw310 + fpga_bitstream: "../objs/lowrisc_systems_chip_earlgrey_cw310_0.1.bit" + force_program_bitstream: False + fw_bin: ../objs/sca_ujson_fpga_cw310.bin + # target_clk_mult is a hardcoded value in the bitstream. Do not change. + target_clk_mult: 0.24 + target_freq: 24000000 + baudrate: 115200 + output_len_bytes: 40 + protocol: "ujson" + port: "/dev/ttyACM_CW310_1" + # Trigger source. + # hw: Precise, hardware-generated trigger - FPGA only. + # sw: Fully software-controlled trigger. + trigger: "sw" +husky: + sampling_rate: 200000000 + num_segments: 20 + num_cycles: 1000 + offset_cycles: 0 + scope_gain: 24 + adc_mul: 1 + decimate: 1 +capture: + scope_select: husky + show_plot: True + plot_traces: 10 + num_traces: 100 + trace_threshold: 10000 + trace_db: ot_trace_library +test: + batch_prng_seed: 6 + key_len_bytes: 40 + text_len_bytes: 40 + plain_text_len_bytes: 40 + masks_off: False + # Currently, 'p256' is the only supported curve. + curve: p256 + # Select the OTBN app to analyze. Currently available: 'keygen', 'modinv' + app: modinv + # For app = keygen: There are two fixed-vs-random test types, KEY and SEED + # Currently batch-mode capture only works with SEED + test_type: SEED + batch_mode: False \ No newline at end of file diff --git a/objs/sca_ujson_fpga_cw310.bin b/objs/sca_ujson_fpga_cw310.bin index 7fb91096..991bdc81 100644 --- a/objs/sca_ujson_fpga_cw310.bin +++ b/objs/sca_ujson_fpga_cw310.bin @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b79fa7125b748b00bc231ecb563e266edf4213eb27ef2318b35315d5c32169b1 -size 304188 +oid sha256:435a989c52dcde6cc2d0cf3b5ffbb1eac4d499c9964d1ce44e5a30ba279ad510 +size 326668 diff --git a/target/communication/sca_otbn_commands.py b/target/communication/sca_otbn_commands.py index bf94ec2a..e821bfd0 100644 --- a/target/communication/sca_otbn_commands.py +++ b/target/communication/sca_otbn_commands.py @@ -6,102 +6,131 @@ Communication with OpenTitan either happens over simpleserial or the uJson command interface. """ +import json +import time +from typing import Optional -class OTOTBNVERT: +class OTOTBN: def __init__(self, target, protocol: str) -> None: self.target = target self.simple_serial = True if protocol == "ujson": self.simple_serial = False + def _ujson_otbn_sca_cmd(self): + # TODO: without the delay, the device uJSON command handler program + # does not recognize the commands. Tracked in issue #256. + time.sleep(0.01) + self.target.write(json.dumps("OtbnSca").encode("ascii")) + + def init(self): + """ Initializes OTBN on the target. + """ + if not self.simple_serial: + # OtbnSca command. + self._ujson_otbn_sca_cmd() + # Init command. + self.target.write(json.dumps("Init").encode("ascii")) + def choose_otbn_app(self, app): """ Select the OTBN application. Args: app: OTBN application """ + # Select the otbn app on the device (0 -> keygen, 1 -> modinv). + app_value = 0x00 + if app == 'modinv': + app_value = 0x01 if self.simple_serial: - # Select the otbn app on the device (0 -> keygen, 1 -> modinv). - if app == 'keygen': - self.target.write(cmd="a", data=bytearray([0x00])) - if app == 'modinv': - self.target.write(cmd="a", data=bytearray([0x01])) - - def write_batch_prng_seed(self, seed): - """ Seed the PRNG. - Args: - seed: The 4-byte seed. - """ - if self.simple_serial: - self.target.write(cmd="s", data=seed) + self.target.write(cmd="a", data=bytearray([app_value])) + else: + # OtbnSca command. + self._ujson_otbn_sca_cmd() + # Ecc256AppSelect command. + self.target.write(json.dumps("Ecc256AppSelect").encode("ascii")) + # App payload. + time.sleep(0.01) + app_select = {"app": app_value} + self.target.write(json.dumps(app_select).encode("ascii")) + time.sleep(0.01) def write_keygen_seed(self, seed): """ Write the seed used for the keygen app. Args: - seed: byte array containing the seed. + seed: Byte array containing the seed. """ - if self.simple_serial: - self.target.write(cmd='x', data=seed) + # OtbnSca command. + self._ujson_otbn_sca_cmd() + # Ecc256SetSeed command. + time.sleep(0.01) + self.target.write(json.dumps("Ecc256SetSeed").encode("ascii")) + # Seed payload. + time.sleep(0.01) + seed_int = [x for x in seed] + seed_data = {"seed": seed_int} + self.target.write(json.dumps(seed_data).encode("ascii")) def write_keygen_key_constant_redundancy(self, const): """ Write the constant redundancy value for the keygen app. Args: - seed: byte array containing the redundancy value. + const: Byte array containing the redundancy value. + const_length: The length of the constant. """ - if self.simple_serial: - self.target.write(cmd="c", data=const) + # OtbnSca command. + self._ujson_otbn_sca_cmd() + # Ecc256SetC command. + self.target.write(json.dumps("Ecc256SetC").encode("ascii")) + # Constant payload. + time.sleep(0.01) + const_int = [x for x in const] + const_data = {"constant": const_int} + self.target.write(json.dumps(const_data).encode("ascii")) - def config_keygen_masking(self, off): + def config_keygen_masking(self, masks_on): """ Disable/enable masking. Args: - off: boolean value. + masks_on: Boolean value. """ - if self.simple_serial: - # Enable/disable masking. - if off is True: - self.target.write(cmd="m", data=bytearray([0x00])) - else: - self.target.write(cmd="m", data=bytearray([0x01])) - - def start_keygen(self, mask): - """ Write the seed mask and start the keygen app. - Args: - mask: byte array containing the mask value. - """ - if self.simple_serial: - # Send the mask and start the keygen operation. - self.target.write('k', mask) - - def start_modinv(self, scalar_k0, scalar_k1): - """ Write the two scalar shares and start the modinv app. - Args: - scalar_k0: byte array containing the first scalar share. - scalar_k1: byte array containing the second scalar share. - """ - if self.simple_serial: - # Start modinv device computation. - self.target.write('q', scalar_k0 + scalar_k1) + # OtbnSca command. + self._ujson_otbn_sca_cmd() + # Ecc256EnMasks command. + self.target.write(json.dumps("Ecc256EnMasks").encode("ascii")) + # Enable/disable masks payload. + time.sleep(0.01) + mask = {"en_masks": masks_on} + self.target.write(json.dumps(mask).encode("ascii")) def start_keygen_batch(self, test_type, num_segments): """ Start the keygen app in batch mode. Args: - test_type: string selecting the test type (KEY or SEED). - num_segments: number of keygen executions to perform. + test_type: String selecting the test type (KEY or SEED). + num_segments: Number of keygen executions to perform. """ - if self.simple_serial: - # Start batch keygen. - if test_type == 'KEY': - self.target.write(cmd="e", data=num_segments) - else: - self.target.write(cmd="b", data=num_segments) + # OtbnSca command. + self._ujson_otbn_sca_cmd() + if test_type == 'KEY': + # Ecc256EcdsaKeygenFvsrKeyBatch command. + self.target.write(json.dumps("Ecc256EcdsaKeygenFvsrKeyBatch").encode("ascii")) + else: + # Ecc256EcdsaKeygenFvsrSeedBatch command. + self.target.write(json.dumps("Ecc256EcdsaKeygenFvsrSeedBatch").encode("ascii")) + # Num traces payload. + time.sleep(0.01) + num_segments_data = {"num_traces": num_segments} + self.target.write(json.dumps(num_segments_data).encode("ascii")) - def read_output(self, len_bytes): - """ Read the output from whichever OTBN app. - Args: - len_bytes: Number of bytes to read. + def read_batch_digest(self): + """ Read d0 from the device. Returns: The received output. """ - if self.simple_serial: - return self.target.read("r", len_bytes, ack=False) + while True: + read_line = str(self.target.readline()) + if "RESP_OK" in read_line: + json_string = read_line.split("RESP_OK:")[1].split(" CRC:")[0] + try: + return json.loads(json_string)["batch_digest"] + except Exception: + pass # noqa: E302