Files
aliro-project/docs/plans/2026-04-20-nucleo-nfc10a1-port.md
Dangerous Things 782074f6ae Initial snapshot: Aliro applet, harness, Nucleo NFC10A1 reader port
Three components, all bench-validated to varying depths:

- applet/: CSA Aliro v1.0 Java Card applet for J3R180. AUTH0 + AUTH1
  expedited-standard flow end-to-end green via PC/SC bench reader
  (aliro-bench-test). Userland AES-256-GCM and HMAC-SHA-256 layered
  on top of J3R180's primitives because the card lacks both natively.
  P-256 curve params seeded explicitly per J3R180's quirk.

- harness/: Python orchestrator (aliro-trustgen, aliro-personalize,
  aliro-bench-test) for trust-bundle generation, card personalization
  via PersonalizationApplet, and PC/SC AUTH0+AUTH1 transactions. 126
  pytest cases passing.

- reader/STM32CubeExpansion_ALIRO_V1_0_0/: ST X-CUBE-ALIRO V1.0.0
  with our NFC10A1 port (NUCLEO-U545RE-Q + X-NUCLEO-NFC10A1, ST25R200
  shared with NFC09A1). nfc10-only/ project, NFC10A1 BSP shim,
  ALIRO_TRUST_OVERRIDE include into vendor's provisioning.c, and an
  ALIRO_APDU_TRACE wrapper around demoTransceiveBlocking. Boots,
  detects the J3R180, completes SELECT + AUTH0; AUTH1 currently fails
  with RFAL ERR_PROTO (0xB) — under investigation, see
  docs/plans/2026-04-20-nucleo-nfc10a1-port.md and bench-notes/.

Excluded: x-cube-aliro.zip vendor archive, harness/.venv, build dirs,
generated aliro_trust.h (contains private reader scalar), all PEMs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 10:17:46 -07:00

22 KiB

Bring up X-NUCLEO-NFC10A1 as an Aliro Reader on NUCLEO-U545RE-Q Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Produce a second independent end-to-end validation of the Aliro applet by running ST's X-CUBE-ALIRO reader firmware on NUCLEO-U545RE-Q + X-NUCLEO-NFC10A1, hitting AUTH0 + AUTH1 green against our personalized J3R180. The PC/SC bench reader (aliro-bench-test) already proves the applet — this adds an MCU-driven reader path.

Architecture: X-NUCLEO-NFC10A1 and X-NUCLEO-NFC09A1 both carry the same ST25R200 NFC reader IC (ST UM3414 covers both boards). X-CUBE-ALIRO already ships a working NFC09A1 project at Projects/nucleo-u5/Applications/Aliro/nfc-only/ that uses Drivers/BSP/Components/st25r200/ — the chip driver is reusable verbatim. The port is board-level only: clone the nfc-only/ project, remap pin/LED assignments from NFC09A1's Arduino header layout to NFC10A1's (plus add an explicit reset pin that NFC10A1 exposes), wire aliro-trustgen's generated header into the build so the reader's long-term keys match what the J3R180 was personalized with, and add instrumentation. No new chip driver fetch is required (an earlier draft of this plan wrongly claimed NFC10A1 was ST25R3916 and would need X-CUBE-NFC7/NFC6 ported in — memory j3r180_ec_curve_seeding.md now captures the correct chip pairings).

Tech Stack: NUCLEO-U545RE-Q (STM32U545RE MCU), X-NUCLEO-NFC10A1 (ST25R200 reader IC), CMake + arm-none-eabi-gcc 13.2+, STM32CubeProgrammer (or st-flash/OpenOCD) for flashing, ST-LINK onboard the Nucleo, RFAL + ST25R200 driver (both already in the X-CUBE-ALIRO vendor tree).


Pre-flight toolchain gaps

Before Task 4 (build) the host needs:

sudo apt install ninja-build

Before Task 5 (flash) the host needs one of:

arm-none-eabi-gcc 13.2.1, cmake 3.28 are already present (verified).


Critical files and locations

Template project (DO NOT MODIFY — clone from here):

Shared code (DO NOT MODIFY — inherited as-is by new project):

  • reader/STM32CubeExpansion_ALIRO_V1_0_0/Drivers/BSP/Components/st25r200/ — chip driver
  • reader/STM32CubeExpansion_ALIRO_V1_0_0/Drivers/BSP/NFC09A1/ — BSP shim (the current project uses NFC09A1's macros; we'll add an NFC10A1 one)
  • reader/STM32CubeExpansion_ALIRO_V1_0_0/Middlewares/ST/rfal/ — RFAL middleware
  • reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/Common/Aliro/ — Aliro protocol layer (where trust is injected in Task 4)

New dirs we will create:

  • reader/STM32CubeExpansion_ALIRO_V1_0_0/Drivers/BSP/NFC10A1/ — BSP shim copied + renamed from NFC09A1
  • reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/nucleo-u5/Applications/Aliro/nfc10-only/ — project root cloned from nfc-only/

NFC10A1 pin-map reference (stm32duino):

  • /tmp/x-nucleo-nfc10a1-ref/examples/X_NUCLEO_NFC10A1_HelloWorld/X_NUCLEO_NFC10A1_HelloWorld.ino (already cloned this session)
  • The #define block in that file is the authoritative Arduino-header pin map for NFC10A1.

Trust bundle: ~/aliro-trust/ (from aliro-trustgen init, same bundle the J3R180 was personalized with).

Harness-side comparison point: aliro-bench-test --trust-dir ~/aliro-trustRESULT: OK is the known-good reference.


Task 1: Clone the BSP shim for NFC10A1

Files:

  • Create: reader/STM32CubeExpansion_ALIRO_V1_0_0/Drivers/BSP/NFC10A1/nfc10a1.h
  • Create: reader/STM32CubeExpansion_ALIRO_V1_0_0/Drivers/BSP/NFC10A1/nfc10a1.c
  • Reference (read-only): reader/STM32CubeExpansion_ALIRO_V1_0_0/Drivers/BSP/NFC09A1/nfc09a1.{h,c}

Step 1: Copy the NFC09A1 shim

cd reader/STM32CubeExpansion_ALIRO_V1_0_0/Drivers/BSP
cp -r NFC09A1 NFC10A1
cd NFC10A1
mv nfc09a1.h nfc10a1.h
mv nfc09a1.c nfc10a1.c

Step 2: Rename identifiers inside the files

sed -i 's/NFC09A1/NFC10A1/g; s/nfc09a1/nfc10a1/g' nfc10a1.h nfc10a1.c

The chip driver include (st25r200.h) must STAY unchanged — same chip. Grep to confirm:

grep -c st25r200 nfc10a1.{h,c}  # should be ≥1 in each file
grep -c st25r500 nfc10a1.{h,c}  # must be 0

Step 3: Verify BSP API surface is unchanged

The top-level Projects/Common/Aliro/Src/app_nfc.c calls e.g. BSP_NFC0XCOMM_SendRecv(...) — those symbol names are generic (NFC0X prefix) and don't change between NFC09A1 and NFC10A1. Confirm by grepping nfc10a1.c for BSP_NFC0XCOMM_ — symbols must match the NFC09A1 original.

Step 4: Commit (if reader/ is under git; currently the project isn't a git repo — create a note file instead)

Since /home/work/VSCodeProjects/aliro_project isn't a git repository, track changes in a plain ledger file at reader/bench-notes/nfc10a1-port-log.md: append a line noting Task 1 completion with a timestamp.


Task 2: Clone nfc-only/nfc10-only/ project

Files:

  • Create: reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/nucleo-u5/Applications/Aliro/nfc10-only/ (whole subtree)

Step 1: Recursive copy

cd reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/nucleo-u5/Applications/Aliro
cp -r nfc-only nfc10-only

Step 2: Rename the per-board Target directory

cd nfc10-only
mv X-CUBE-NFC9 X-CUBE-NFC10

Step 3: Update nfc_conf.h for NFC10A1 layout

Edit nfc10-only/X-CUBE-NFC10/Target/nfc_conf.h:

  • Change include guard: __NFC09A1_CONF_H____NFC10A1_CONF_H__

  • Rename every NFC09A1_* macro to NFC10A1_* (LED1-LED6 RCC clock enables + pin definitions)

  • Pin remap per NFC10A1 Arduino header:

    Function NFC09A1 (current) NFC10A1 (new) Nucleo-U545RE-Q pin
    SPI CS (ST25R_SS) PC9 (line 87-88) D10 → PB6 (verify against stm32u5xx_nucleo_bus.h) update
    SPI IRQ (ST25R_INT) PA0 (line 89-90) A0 → PA0 (same) unchanged
    Reset (NEW — NFC10A1 exposes) D8 → PA9 (typical U5 mapping; verify) add new
    LED A PB6 A2 → PA4 (verify) update
    LED B PB0 A3 → PB0 (may be same) verify
    LED F PB10 D5 → PB4 (verify) update
    LED V PA8 D7 → PA8 (may be same) verify
    LED AP2P PB10 D6 → PB10 (may be same) verify
    LED FIELD PA1 A1 → PA1 (may be same) verify

    Authoritative source for Arduino→MCU mapping: reader/STM32CubeExpansion_ALIRO_V1_0_0/Drivers/BSP/NUCLEO-U545RE-Q/stm32u5xx_nucleo_bus.h — open it and grep for ARDUINO_D10_PIN, ARDUINO_A0_PIN, etc. If the header uses macros like ARDUINO_Dx_Pin / ARDUINO_Ax_Pin, reference those directly (avoids hardcoding PAx literals that could drift).

  • Add reset pin definitions (NFC10A1-specific, absent from NFC09A1):

    #define NFC10A1_RESET_PIN         GPIO_PIN_9   /* or whatever D8 resolves to */
    #define NFC10A1_RESET_PIN_PORT    GPIOA
    #define NFC10A1_RESET_PIN_CLK_ENABLE()  __HAL_RCC_GPIOA_CLK_ENABLE()
    

    Plumb the reset line through to the st25r200 driver if the driver has a reset hook (check st25r200.c for a function like st25r200_Reset() or similar; the NFC12 template uses BUS_SPI1_RST_GPIO_PIN per the explorer's diff).

  • Include path update: #include "rfal_defConfig.h" stays the same (shared); the board-specific bits should now reference nfc10a1.h (not nfc09a1.h) if included.

Step 4: Update rfal_platform.h

Open nfc10-only/X-CUBE-NFC10/Target/rfal_platform.h. The platform macros (platformSpiSelect, platformSpiTxRx, platformProtectST25RComm) bind to ST25R_SS_PIN/PORT etc. — those come from nfc_conf.h so if the pins there are right, this file just needs the header guard rename (and any NFC09A1/NFC9 references → NFC10A1/NFC10).

Step 5: Update CMake

nfc10-only/cmake/stm32cubemx/CMakeLists.txt:

  • Change project/target name: any nfc-only or nfc_onlynfc10-only / nfc10_only
  • Swap include dir: Drivers/BSP/NFC09A1Drivers/BSP/NFC10A1
  • Update Target include: X-CUBE-NFC9/TargetX-CUBE-NFC10/Target

The ST25R200 CMake define stays — same chip.

nfc10-only/CMakeLists.txt (top-level): update project name.

Step 6: Update main.c init

In nfc10-only/Core/Src/main.c, search for NFC09A1_ or BSP_NFC09A1_ and replace with NFC10A1_ / BSP_NFC10A1_ equivalents. The BSP_NFC0XCOMM_* generic names stay.

Step 7: Append to the port-log ledger

reader/bench-notes/nfc10a1-port-log.md

Task 3: First build — iterate on errors

Files: no new files; fix whatever the build surfaces.

Step 1: Configure

cd reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/nucleo-u5/Applications/Aliro/nfc10-only
cmake -B build -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE=cmake/stm32cubemx/gcc-arm-none-eabi.cmake \
  -DCMAKE_BUILD_TYPE=Debug

(If ninja wasn't installed, fall back to -G "Unix Makefiles" and use cmake --build build / make -j.)

Step 2: Build and triage

cmake --build build 2>&1 | tee build.log

Expected first-pass errors (iterate):

  • Undefined reference to BSP_NFC10A1_Init or similar → Task 1 rename incomplete; grep for stale NFC09A1 in the new shim.
  • nfc09a1.h: No such file → stale include somewhere in nfc10-only; replace with nfc10a1.h.
  • st25r200.h: No such file should NOT appear — if it does, the Components include path was accidentally dropped in Task 2 Step 5.
  • ARDUINO_Dx_Pin undefined → the pin macro doesn't exist on this board's stm32u5xx_nucleo_bus.h; fall back to hardcoded PAx literals per NUCLEO-U545RE-Q schematic (UM3414 App A for pinout tables).
  • Reset pin symbol unused → you defined NFC10A1_RESET_* but didn't wire it into init. Either remove the def if the driver doesn't need it, or add a HAL_GPIO_WritePin pulse in nfc10a1.c::BSP_NFC10A1_Init().

Fix one class of error at a time.

Step 3: Confirm artifacts produced

ls -la build/nfc10-only.elf build/nfc10-only.bin

Expected: both exist, .bin ~100-200 KB.

Step 4: Record build success

Append to reader/bench-notes/nfc10a1-port-log.md.


Task 4: Wire aliro-trustgen output into the firmware build

Files:

  • Possibly modify: reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/Common/Aliro/Src/provisioning.c (or equivalent — find the hardcoded trust first)
  • Create: reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/nucleo-u5/Applications/Aliro/nfc10-only/Core/Inc/aliro_trust.h (gitignored, generated)
  • Possibly modify: harness/src/aliro_harness/trustgen/header.py (to add an emit-header CLI subcommand if missing)
  • Modify: nfc10-only/cmake/stm32cubemx/CMakeLists.txt (add custom_command)

Step 1: Find the hardcoded trust

grep -rln "reader_priv\|reader_pub\|issuer_pub\|credential_issuer" \
  reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/Common/Aliro/

Expected: one or two files in Src/ with static const uint8_t byte arrays. Note extern names — the generated header must match.

Step 2: Verify aliro-trustgen can emit the expected header

aliro-trustgen --help
aliro-trustgen init --out-dir /tmp/trust-test
ls /tmp/trust-test/

If there's already an emit-header subcommand (or init writes aliro_trust.h), use it. If not, add a one-liner subcommand that reads reader.pem + access_credential.pem + reader_group_id.bin + reader_group_sub_id.bin and writes a C header matching the vendor's extern names.

A bare-minimum template at harness/src/aliro_harness/trustgen/header.py::emit_c_header(trust_dir, out_path):

def emit_c_header(trust_dir: Path, out_path: Path) -> None:
    rp = load_priv(trust_dir / "reader.pem")          # 32 bytes
    rP = load_pubk(trust_dir / "reader.pem")          # 64 bytes x||y
    iP = load_pubk(trust_dir / "access_credential.pem")  # 64 bytes x||y
    gid = (trust_dir / "reader_group_id.bin").read_bytes()     # 16 bytes
    sid = (trust_dir / "reader_group_sub_id.bin").read_bytes() # 16 bytes
    with out_path.open("w") as f:
        f.write("/* Auto-generated by aliro-trustgen — do not edit */\n")
        f.write("#pragma once\n#include <stdint.h>\n\n")
        f.write(f"static const uint8_t reader_priv_key[32] = {{ {fmt(rp)} }};\n")
        f.write(f"static const uint8_t reader_pub_key[64] = {{ {fmt(rP)} }};\n")
        f.write(f"static const uint8_t issuer_pub_key[64] = {{ {fmt(iP)} }};\n")
        f.write(f"static const uint8_t reader_group_id[16] = {{ {fmt(gid)} }};\n")
        f.write(f"static const uint8_t reader_group_sub_id[16] = {{ {fmt(sid)} }};\n")

Plus a Click command that calls it. Write tests in harness/tests/test_trustgen_header.py.

Step 3: Replace vendor hardcoded bytes with the generated header

In the file found in Step 1:

// was: static const uint8_t reader_priv_key[32] = { 0xAA, 0xBB, ... };
#include "aliro_trust.h"

(If vendor declared its trust bytes as static const and consumed them locally, the header defining the same names with static const linkage is a drop-in. If vendor declared them extern, swap the header to match.)

Step 4: CMake custom_command to regenerate on trust change

In nfc10-only/cmake/stm32cubemx/CMakeLists.txt:

set(ALIRO_TRUST_DIR "$ENV{HOME}/aliro-trust" CACHE PATH "Trust bundle directory")
set(ALIRO_TRUST_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Inc/aliro_trust.h")

add_custom_command(
  OUTPUT "${ALIRO_TRUST_HEADER}"
  COMMAND aliro-trustgen emit-header --trust-dir "${ALIRO_TRUST_DIR}" --out "${ALIRO_TRUST_HEADER}"
  DEPENDS "${ALIRO_TRUST_DIR}/reader.pem" "${ALIRO_TRUST_DIR}/access_credential.pem"
  COMMENT "Regenerating aliro_trust.h from ${ALIRO_TRUST_DIR}"
  VERBATIM
)
add_custom_target(aliro_trust_header DEPENDS "${ALIRO_TRUST_HEADER}")
add_dependencies(${CMAKE_PROJECT_NAME}.elf aliro_trust_header)

Step 5: Ignore the generated header

# Since project isn't git, create a LOCAL .gitignore-equivalent ledger:
echo "Projects/nucleo-u5/Applications/Aliro/nfc10-only/Core/Inc/aliro_trust.h" \
  >> reader/STM32CubeExpansion_ALIRO_V1_0_0/.distignore

(Documentational — mostly a reminder.)

Step 6: Rebuild + verify the bytes made it into the ELF

rm -rf build && cmake -B build -G Ninja ...
cmake --build build
arm-none-eabi-objdump -s -j .rodata build/nfc10-only.elf | grep -A2 reader_priv

Expected: 32 bytes matching the first 32 bytes of the PEM-decoded reader private scalar.


Task 5: Flash + first-boot verification (HARDWARE REQUIRED)

Files: none — on-device only.

Step 1: Connect hardware

  • NUCLEO-U545RE-Q to host USB (enumerates as /dev/ttyACM0 for VCP + ST-LINK DFU)
  • X-NUCLEO-NFC10A1 plugged onto Arduino header (ensure correct orientation — D0 pin aligned)

Step 2: Flash

# With STM32CubeProgrammer:
STM32_Programmer_CLI -c port=SWD -w build/nfc10-only.elf -v -rst
# Or with st-flash:
arm-none-eabi-objcopy -O binary build/nfc10-only.elf build/nfc10-only.bin
st-flash write build/nfc10-only.bin 0x08000000

Step 3: Open UART console

minicom -D /dev/ttyACM0 -b 115200
# or: picocom -b 115200 /dev/ttyACM0

Expected: within a second of reset, banner text like Aliro Reader — NFC10A1 / ST25R200 followed by init messages and a polling loop.

Step 4: Verify NFC field activates

Within a few seconds of boot the ST25R200 should drive the antenna. NFC10A1 has a FIELD LED — expect it to light solid (or blink during active polling).

Step 5: If the FIELD LED doesn't light

Common causes — debug in order:

  1. Chip-ID read returns 0xFF → SPI isn't talking (check CS pin resolves to the right MCU GPIO; probe with logic analyzer if available)
  2. Reset pin held low → the reset wiring added in Task 2 isn't pulsed correctly
  3. Antenna tuning failed → check UART log for specific RFAL init error code

Step 6: Stop here if field isn't coming up — no point chasing the protocol layer yet.


Task 6: Empty-field + random-card discovery (HARDWARE REQUIRED)

Step 1: With no card present — UART should log periodic polling, no target (or equivalent). Every ~500ms.

Step 2: Present a random NFC-A card (transit, hotel keycard) — UART should log target detected: NFC-A, UID=.... Proves polling + anti-collision.

Step 3: Present the J3R180 — UART should log target detected and attempt a SELECT of the Aliro AID. Expected either success (proceed to Task 7) or an SW that points at which APDU exchange failed.

Step 4: Capture UART log to reader/bench-notes/nfc10a1-discovery.log.


Task 7: Full AUTH0+AUTH1 against J3R180 (HARDWARE REQUIRED)

Step 1: Confirm baseline still green

aliro-bench-test --trust-dir ~/aliro-trust

Expected: RESULT: OK. If not, stop — something regressed in the applet.

Step 2: Ensure card personalized from same trust bundle

(No action if card hasn't been re-personalized since Task 4 wired the same bundle into the firmware. If uncertain, re-run aliro-personalize --trust-dir ~/aliro-trust on a fresh card.)

Step 3: Present J3R180 to the Nucleo reader

Expected UART log sequence:

target detected: NFC-A
SELECT Aliro AID → SW=9000
AUTH0 (lc=N) → response N bytes, SW=9000
AUTH1 (lc=N) → response N bytes, SW=9000
AUTH1 decrypt OK
UD signature verify OK
GRANT — signaling_bitmap=0x0005

Step 4: Failure triage

  • SW=6A82 on SELECT — AID mismatch. Vendor firmware's SELECT target must be A0000009 09 ACCE 55 01.
  • SW=6F00 on AUTH0 — uncaught applet exception; correlate with bench-test behavior, should NOT appear if bench-test is green.
  • AUTH1 decrypt fail — ExpeditedSKDevice mismatch. Either firmware's trust bundle differs from the card's, or Table 8-12 bytes are assembled differently. Compare Table 8-12 hex from firmware UART log vs bench-test --debug output.
  • Signature verify fail after decrypt — firmware's issuer_pub_key / credential_pub_key extern doesn't match the trust bundle used to personalize the card.

Step 5: On success

Capture UART log to reader/bench-notes/nfc10a1-auth1-green.log. Update memory pcsc_bench_reader.md: append a note that Nucleo-NFC10A1 is now a second validation path.


Task 8: Instrumentation — UART APDU trace + LED state machine

Files:

  • Create: reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/Common/Aliro/Inc/aliro_log.h
  • Modify: reader/STM32CubeExpansion_ALIRO_V1_0_0/Projects/Common/Aliro/Src/app_nfc.c — add APDU log points around each transceive
  • Modify: nfc10-only/Core/Src/main.c — LED helpers

Step 1: aliro_log.h

#pragma once
#include <stdio.h>
#include <stdint.h>

#define ALIRO_LOG_INFO(fmt, ...)   printf("[INFO] " fmt "\n", ##__VA_ARGS__)
#define ALIRO_LOG_ERROR(fmt, ...)  printf("[ERROR] " fmt "\n", ##__VA_ARGS__)

static inline void aliro_log_hex(const char *dir, const uint8_t *buf, uint16_t len) {
    printf("[APDU %s] ", dir);
    for (uint16_t i = 0; i < len; i++) printf("%02x", buf[i]);
    printf("\n");
}

Step 2: Instrument the APDU pump

In app_nfc.c (or wherever ISO-DEP TX/RX happens), wrap:

aliro_log_hex(">", txBuf, txLen);
// ... transceive
aliro_log_hex("<", rxBuf, rxLen);

Match the format the bench-test logs emit (see harness/src/aliro_harness/reader/transaction.py).

Step 3: LED helpers

In nfc10-only/Core/Src/main.c:

static void aliro_led_grant(void) { HAL_GPIO_WritePin(PLATFORM_LED_A_PORT, PLATFORM_LED_A_PIN, GPIO_PIN_SET); }
static void aliro_led_deny(void)  { HAL_GPIO_WritePin(PLATFORM_LED_B_PORT, PLATFORM_LED_B_PIN, GPIO_PIN_SET); }
static void aliro_led_active(bool on) { HAL_GPIO_WritePin(PLATFORM_LED_FIELD_PORT, PLATFORM_LED_FIELD_PIN, on ? GPIO_PIN_SET : GPIO_PIN_RESET); }

Call from the Aliro state machine: aliro_led_active(true) at AUTH0 start, grant/deny at AUTH1 finish.

Step 4: Verify visually

J3R180 → FIELD LED (A1) lights during transaction, LED A (A2) lights for ~1s on grant, clears. Random card → LED B (A3) lights on AUTH0 failure.


Task 9: A/B compare — Nucleo vs PC/SC bench reader

Files:

Step 1: Bench-test transcript with APDU trace

If --debug doesn't exist, add it (one or two lines in the transmit wrapper at cli.py to click.echo(f"[APDU >] {apdu.hex()}", err=True) + same for response).

aliro-bench-test --trust-dir ~/aliro-trust --debug 2> /tmp/bench-trace.log

Step 2: Nucleo transcript

# Present card DURING this capture:
(timeout 10 cat /dev/ttyACM0) > /tmp/nucleo-trace.log

Step 3: Diff APDU streams

diff <(grep '^\[APDU' /tmp/bench-trace.log) <(grep '^\[APDU' /tmp/nucleo-trace.log)

Expected: differences only in randomized fields (reader ephemeral pubkey, transaction_identifier, encrypted payload ciphertext). Fixed fields (SELECT AID, command_parameters, protocol_version, reader_identifier, response TLV tags) match byte-for-byte.

Step 4: Document findings

reader/bench-notes/ab-compare-2026-04-XX.md captures:

  • Same card + same trust bundle → both GRANT
  • APDU diff: expected-only deltas
  • Any surprise (extra TLV, unexpected field order, SW difference)

Follow-up work NOT in this plan

  • Step-up phase on the Nucleo (SELECT STEP_UP + CBOR ENVELOPE — large, separate plan)
  • Antenna tuning against DT's implantable form factor (different tuning than external cards)
  • CI build: compile the ELF in CI to catch regressions (no probe needed, just compile)
  • Structured binary telemetry log format (replace printf — nice-to-have, not needed for bench)

Verification checkpoint after all tasks complete

  • nfc10-only.elf builds cleanly from the vendor tree
  • Flasher writes and reader boots with NFC field active
  • aliro-bench-test --trust-dir ~/aliro-trust still green (no applet regressions)
  • Nucleo reader → J3R180 → GRANT + full APDU log + LED state
  • A/B diff shows only expected randomness
  • Memory entry updated: Nucleo-NFC10A1 is a second validation path alongside PC/SC