feat: aliro-bench-profile CLI + per-APDU latency probes
New `aliro-bench-profile` entry point drives the applet's INS_DIAG_* commands over PC/SC at two iteration counts (N=4 / N=16 by default) and computes per-op cost as the slope, factoring out APDU round-trip + any one-time Signature.init setup. Reports HMAC, ECDH, ECDSA sign, and AES-GCM 137B costs side by side, then reconstructs an AUTH1 budget using the per-primitive counts from the applet's processAuth1 flow so we can compare the reconstructed estimate against bench-test wall-clock and see how much of AUTH1 is crypto vs TLV parsing / I/O. Also extends `aliro-bench-test` to time SELECT / AUTH0 / AUTH1 / total via time.perf_counter() bracketing each transmit() call. The TransactionResult now carries a latencies_ms dict, threaded through all return sites so both success and failure paths print the new "APDU latencies (ms):" block. Made the 1.78x AUTH1 wall-clock speedup from the 4-bit GHASH commit quantifiable on real hardware (5,795 -> 3,244 ms). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,7 @@ def main(trust_dir: Path | None, reader_index: int, list_readers: bool) -> None:
|
||||
f"RESULT: FAIL \u2014 {result.error}", err=True
|
||||
)
|
||||
_echo_tlv_breakdown(result)
|
||||
_echo_latencies(result)
|
||||
click.echo(
|
||||
"Hint: the AUTH1 response decrypted, so session keys are "
|
||||
"correct. The signature verification failed — check that the "
|
||||
@@ -103,10 +104,16 @@ def main(trust_dir: Path | None, reader_index: int, list_readers: bool) -> None:
|
||||
err=True,
|
||||
)
|
||||
raise click.exceptions.Exit(1)
|
||||
raise click.ClickException(result.error or "Aliro transaction failed.")
|
||||
# Early failure (e.g. non-9000 SW on SELECT/AUTH0/AUTH1) — print
|
||||
# whatever per-APDU latencies we collected before bailing so the
|
||||
# operator can see how long the failing step actually took.
|
||||
click.echo(f"RESULT: FAIL — {result.error}", err=True)
|
||||
_echo_latencies(result)
|
||||
raise click.exceptions.Exit(1)
|
||||
|
||||
click.echo("RESULT: OK \u2014 applet round-trip on real hardware.")
|
||||
_echo_tlv_breakdown(result)
|
||||
_echo_latencies(result)
|
||||
|
||||
|
||||
def _echo_tlv_breakdown(result) -> None:
|
||||
@@ -116,3 +123,16 @@ def _echo_tlv_breakdown(result) -> None:
|
||||
click.echo(f" 0x5A credential_PubK: {cred_pub_len}B")
|
||||
click.echo(f" 0x9E UD signature: {ud_sig_len}B")
|
||||
click.echo(f" 0x5E signaling_bitmap: 0x{bitmap_hex}")
|
||||
|
||||
|
||||
def _echo_latencies(result) -> None:
|
||||
"""Per-APDU wall-clock latencies. Surfaces card-side processing time \u2014
|
||||
the AUTH1 number is what matters for the RFAL WTX-cap analysis."""
|
||||
if not result.latencies_ms:
|
||||
return
|
||||
click.echo(" APDU latencies (ms):")
|
||||
for step in ("select", "auth0", "auth1"):
|
||||
if step in result.latencies_ms:
|
||||
click.echo(f" {step:<6} {result.latencies_ms[step]:7.1f}")
|
||||
total = sum(result.latencies_ms.values())
|
||||
click.echo(f" {'total':<6} {total:7.1f}")
|
||||
|
||||
@@ -7,8 +7,9 @@ a mocked/in-process card.
|
||||
"""
|
||||
|
||||
import secrets
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
@@ -123,6 +124,10 @@ class TransactionResult:
|
||||
ud_sig: bytes | None = None # 0x9E value (64B raw r||s)
|
||||
signaling_bitmap: bytes | None = None # 0x5E value (2B)
|
||||
error: str | None = None # populated on failure
|
||||
# Wall-clock per-APDU latencies in milliseconds. Populated as each step
|
||||
# runs, so a partial result on failure still surfaces what we measured
|
||||
# before the failing step. Keys: "select", "auth0", "auth1".
|
||||
latencies_ms: dict[str, float] = field(default_factory=dict)
|
||||
|
||||
|
||||
def run_aliro_transaction(
|
||||
@@ -140,14 +145,22 @@ def run_aliro_transaction(
|
||||
|
||||
Any non-9000 SW or signature mismatch returns ok=False with ``error`` set.
|
||||
"""
|
||||
# Collect per-APDU wall-clock latencies; mutated in place and threaded
|
||||
# through every TransactionResult so partial timings surface on failure.
|
||||
latencies: dict[str, float] = {}
|
||||
|
||||
# SELECT EXPEDITED
|
||||
select_apdu = (
|
||||
bytes([0x00, 0xA4, 0x04, 0x00, len(EXPEDITED_AID)]) + EXPEDITED_AID
|
||||
)
|
||||
_t = time.perf_counter()
|
||||
_, sw = transmit(select_apdu)
|
||||
latencies["select"] = (time.perf_counter() - _t) * 1000.0
|
||||
if sw != SW_OK:
|
||||
return TransactionResult(
|
||||
ok=False, error=f"SELECT EXPEDITED failed: SW=0x{sw:04X}"
|
||||
ok=False,
|
||||
error=f"SELECT EXPEDITED failed: SW=0x{sw:04X}",
|
||||
latencies_ms=latencies,
|
||||
)
|
||||
|
||||
# Generate reader ephemeral + transaction state
|
||||
@@ -168,9 +181,15 @@ def run_aliro_transaction(
|
||||
command_parameters=0x00,
|
||||
authentication_policy=0x00,
|
||||
)
|
||||
_t = time.perf_counter()
|
||||
auth0_resp_data, sw = transmit(build_auth0_apdu(auth0_data))
|
||||
latencies["auth0"] = (time.perf_counter() - _t) * 1000.0
|
||||
if sw != SW_OK:
|
||||
return TransactionResult(ok=False, error=f"AUTH0 failed: SW=0x{sw:04X}")
|
||||
return TransactionResult(
|
||||
ok=False,
|
||||
error=f"AUTH0 failed: SW=0x{sw:04X}",
|
||||
latencies_ms=latencies,
|
||||
)
|
||||
|
||||
cred_ephem_pub_uncompressed = find_top_level(auth0_resp_data, 0x86)
|
||||
if (
|
||||
@@ -180,6 +199,7 @@ def run_aliro_transaction(
|
||||
return TransactionResult(
|
||||
ok=False,
|
||||
error="AUTH0 response missing or malformed 0x86 credential_ePubK",
|
||||
latencies_ms=latencies,
|
||||
)
|
||||
cred_ephem_pub_x = cred_ephem_pub_uncompressed[1:33]
|
||||
|
||||
@@ -210,16 +230,24 @@ def run_aliro_transaction(
|
||||
)
|
||||
raw_sig = sign_table_812_raw(table_812, bundle.reader_priv)
|
||||
auth1_data = build_auth1_data(raw_sig, command_parameters=0x01)
|
||||
_t = time.perf_counter()
|
||||
auth1_resp_data, sw = transmit(build_auth1_apdu(auth1_data))
|
||||
latencies["auth1"] = (time.perf_counter() - _t) * 1000.0
|
||||
if sw != SW_OK:
|
||||
return TransactionResult(ok=False, error=f"AUTH1 failed: SW=0x{sw:04X}")
|
||||
return TransactionResult(
|
||||
ok=False,
|
||||
error=f"AUTH1 failed: SW=0x{sw:04X}",
|
||||
latencies_ms=latencies,
|
||||
)
|
||||
|
||||
# Decrypt + verify
|
||||
try:
|
||||
plaintext = decrypt_auth1_response(sk_device, auth1_resp_data)
|
||||
except Exception as e:
|
||||
return TransactionResult(
|
||||
ok=False, error=f"AUTH1 response decrypt failed: {e}"
|
||||
ok=False,
|
||||
error=f"AUTH1 response decrypt failed: {e}",
|
||||
latencies_ms=latencies,
|
||||
)
|
||||
|
||||
cred_pub_5a = find_top_level(plaintext, 0x5A)
|
||||
@@ -231,12 +259,14 @@ def run_aliro_transaction(
|
||||
ok=False,
|
||||
error="Table 8-11 missing 0x9E UD signature",
|
||||
plaintext=plaintext,
|
||||
latencies_ms=latencies,
|
||||
)
|
||||
if bitmap is None:
|
||||
return TransactionResult(
|
||||
ok=False,
|
||||
error="Table 8-11 missing 0x5E signaling_bitmap",
|
||||
plaintext=plaintext,
|
||||
latencies_ms=latencies,
|
||||
)
|
||||
|
||||
table_813 = build_table_813(
|
||||
@@ -258,6 +288,7 @@ def run_aliro_transaction(
|
||||
credential_pub=cred_pub_5a,
|
||||
ud_sig=ud_sig,
|
||||
signaling_bitmap=bitmap,
|
||||
latencies_ms=latencies,
|
||||
)
|
||||
|
||||
return TransactionResult(
|
||||
@@ -266,4 +297,5 @@ def run_aliro_transaction(
|
||||
credential_pub=cred_pub_5a,
|
||||
ud_sig=ud_sig,
|
||||
signaling_bitmap=bitmap,
|
||||
latencies_ms=latencies,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user