Files
n_signer/plans/teensy41_signer_port.md

249 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plan: Port n_signer to Teensy 4.1 with SDXC 1 TB exFAT OTP Pad
## Goal
Port the n_signer hardware signer to the **Teensy 4.1** (NXP i.MX RT1062,
Cortex-M7 @ 600 MHz), using its built-in SD slot to hold a **1 TB SDXC exFAT
one-time-pad file**. The Teensy 4.1 is the high-capacity-OTP target; the CYD
(classic ESP32) remains the cheap/low-power option with the mnemonic-derived
stream pad.
The on-the-wire protocol is identical to the host and the CYD/feather firmware
([`README.md`](../README.md) §4 — the algorithm-based API). The auth envelope,
verb set, enforcement matrix, `key_id` convention, and structured-result JSON
are all unchanged.
## Why the Teensy 4.1
| Concern | Teensy 4.1 | CYD (ESP32) |
|---|---|---|
| MCU | 600 MHz Cortex-M7 | 240 MHz Xtensa LX6 |
| SRAM | 1 MB + 16 MB PSRAM (on-board) | 512 KB, no PSRAM |
| SD slot | **4-bit SDMMC, exFAT via SdFat, up to 2 TB** | 1-bit SDSPI, FAT32, up to 32 GB |
| SD speed | ~20-40 MB/s | ~2 MB/s |
| USB | Hi-Speed (480 Mbps) device + host | CH340 UART only |
| WiFi | **None** | Yes (unused) |
| PQ crypto | ~1-2 s SLH-DSA-128s | 5-30 s SLH-DSA-128s |
| Display | Add SPI ILI9341 (same panel as CYD) | Built-in 2.8" ILI9341 + touch |
The decisive factor is the **SD slot**: PJRC's [SdFat](https://github.com/greiman/SdFat)
library has native exFAT support, so a 1 TB SDXC card (which ships formatted
exFAT) mounts and reads/writes directly — no reformatting, no exFAT driver
work. The 4-bit SDMMC bus is fast enough (~20-40 MB/s) for pad reads with
arbitrary seeks.
## Hardware
### Board
- **Teensy 4.1** (PJRC) — $27. Has 8 MB flash, 16 MB external QSPI, 16 MB PSRAM
(soldered), 1 MB internal SRAM, native USB Hi-Speed, built-in SD slot, 10/100
Ethernet PHY, no WiFi/BT.
### Display + touch (add-on)
- **4.0" ST7796S 480×320 with XPT2046 resistive touch** (Hosyond or equivalent,
~$12-15). Specs: 4-wire SPI, RGB 65K, 3.3V~5V (works at Teensy's 3.3V logic),
XPT2046 resistive touch, includes touch pen + SD card slot on the module.
SPI wiring to the Teensy 4.1:
- TFT: MOSI=pin 11, SCK=pin 13, MISO=pin 12, CS=pin 10, DC=pin 9,
RESET=pin 8, BL=pin 22 (PWM via analogWrite)
- Touch (XPT2046, shared SPI bus): T_CS=pin 7, T_IRQ=pin 6,
T_CLK=pin 13, T_MOSI=pin 11, T_MISO=pin 12
- Power: VCC=3.3V, GND=GND
- The ST7796S controller needs a different init sequence than the CYD's
ILI9341, and the resolution is 480×320 (not 320×240). The XPT2046 touch
driver ports from [`firmware/cyd_esp32_2432s028/main/touch.c`](../firmware/cyd_esp32_2432s028/main/touch.c)
with new resolution constants.
- The module's on-board SD card slot is a bonus (backup pad / offset file),
but the 1 TB pad uses the Teensy's built-in SD slot (4-bit SDMMC, faster).
### SD card
- **1 TB microSDXC** (exFAT, ~$60-80) in the Teensy's built-in slot.
- The pad file (`/pad.bin`) + offset file (`/pad.offset`) live on this card.
### USB transport
- The Teensy's native USB port (device mode) exposes a **CDC-ACM serial** +
optional **WebUSB vendor** interface (TinyUSB composite), same framing as the
feather/CYD (4-byte big-endian length prefix + JSON-RPC payload).
- The host sees `/dev/ttyACM0` (Linux) or `COMx` (Windows).
## Target directory layout
```
firmware/teensy41/
├── README.md (created)
├── teensy41_signer.ino (Arduino entry, or main.cpp for PlatformIO)
├── src/
│ ├── main.cpp (app loop: UI → transport → dispatch)
│ ├── dispatch.cpp (verb dispatch — ported from cyd main.c handle_request)
│ ├── dispatch.h
│ ├── key_derivation.cpp (BIP-39 → seed → secp256k1/ed25519/x25519/PQ keys)
│ ├── key_derivation.h
│ ├── pq_crypto.cpp (PQClean wrappers: ml-dsa-65, slh-dsa-128s, ml-kem-768)
│ ├── pq_crypto.h
│ ├── otp_pad_sd.cpp (SDXC exFAT pad: mount, read, offset persistence)
│ ├── otp_pad_sd.h
│ ├── transport.cpp (USB CDC + length-prefix framing)
│ ├── transport.h
│ ├── display.cpp (ILI9341 driver — ported from cyd ili9341.c)
│ ├── display.h
│ ├── touch.cpp (XPT2046 driver — ported from cyd touch.c)
│ ├── touch.h
│ ├── ui.cpp (LVGL or hand-rolled UI — ported from cyd ui.c)
│ ├── ui.h
│ ├── secure_mem.cpp (zeroize helpers)
│ ├── secure_mem.h
│ ├── bech32.cpp (npub encoding)
│ ├── bech32.h
│ ├── mnemonic.cpp (BIP-39 wordlist + validation)
│ ├── mnemonic.h
│ └── mnemonic_wordlist.h
├── lib/
│ ├── secp256k1/ (libsecp256k1, built for ARM Cortex-M7)
│ ├── pqclean/ (PQClean ML-DSA-65, SLH-DSA-128s, ML-KEM-768)
│ ├── nostr_core_lib/ (symlink/copy of resources/nostr_core_lib)
│ └── SdFat/ (PJRC SdFat with exFAT — via Arduino Library Manager)
└── platformio.ini (or Arduino project config)
```
## Architecture
```mermaid
flowchart TD
USB[Host USB CDC] --> Frame[transport.cpp<br/>length-prefix framing]
Frame --> Auth[auth envelope verify<br/>secp256k1 schnorr]
Auth -->|ok| Disp[dispatch.cpp<br/>handle_request]
Auth -->|fail| Err[auth error]
Disp -->|nostr_*| NIP[Nostr verbs<br/>secp256k1 NIP-06]
Disp -->|alg verb| Alg[Algorithm verbs<br/>algorithm + index]
Alg -->|otp| OTP[otp_pad_sd.cpp<br/>SDXC exFAT pad]
Alg -->|secp256k1/ed25519/x25519/PQ| KD[key_derivation.cpp]
OTP --> SD[(1 TB SDXC<br/>exFAT<br/>/pad.bin + /pad.offset)]
KD --> UI[ui.cpp<br/>approval prompt<br/>ILI9341 + XPT2046]
UI -->|approve| Exec[execute verb]
Exec --> Resp[structured JSON result]
Resp --> Frame
```
## Implementation phases
### Phase 1: Board bring-up (display + touch + SD)
1. **Toolchain:** Arduino CLI + Teensyduino (simplest), or PlatformIO with the
`teensy` platform. Verify `Blink` + `HelloSerial` compile and flash.
2. **ILI9341 display:** port [`firmware/cyd_esp32_2432s028/main/ili9341.c`](../firmware/cyd_esp32_2432s028/main/ili9341.c)
to Teensy GPIO + SPI (use `SPI.beginTransaction` for 40 MHz HSPI). Exit
criterion: fill screen + draw text.
3. **XPT2046 touch:** port [`firmware/cyd_esp32_2432s028/main/touch.c`](../firmware/cyd_esp32_2432s028/main/touch.c).
Exit criterion: read touch coordinates, map to 320×240.
4. **SD card + exFAT:** install SdFat via Arduino Library Manager. Mount a 1 TB
SDXC card (exFAT), write + read a test file. Exit criterion:
`sd.begin(SdioConfig(FIFO_SDIO))` succeeds on a 1 TB card, `file.open`
+ `file.write` + `file.read` round-trips.
### Phase 2: Crypto stack port
1. **secp256k1:** build `libsecp256k1` for ARM Cortex-M7 (no ASM, pure C with
`USE_NUM_NONE` / `USE_FIELD_INV_BUILTIN`). Verify schnorr sign/verify +
ECDSA sign/verify against known test vectors.
2. **ed25519 / x25519:** use a portable ed25519 (e.g. the ref10 impl or
`crypto_mbedtls` if mbedtls is available for Teensy — alternatively
[`micro-ecc`](https://github.com/kmackay/micro-ecc) + a portable ed25519).
Verify against the host's test vectors.
3. **PQClean:** compile `resources/pqclean/` (ML-DSA-65, SLH-DSA-128s,
ML-KEM-768) for Cortex-M7. The `crypto_backend` abstraction needs a Teensy
backend (`crypto_backend_sdfat.c` or reuse the vendored Keccak from
[`resources/pqclean/common/crypto_backend_mbedtls.c`](../resources/pqclean/common/crypto_backend_mbedtls.c)
— the Keccak core is portable C). SHA-256/512 via SdFat's built-in or a
portable impl. Verify keygen + sign + verify against the host.
4. **nostr_core_lib:** compile [`resources/nostr_core_lib/`](../resources/nostr_core_lib/)
(nip004, nip044, nostr_common, utils, crypto) for Cortex-M7. This is
portable C and should compile as-is.
### Phase 3: Key derivation + mnemonic
1. Port [`firmware/cyd_esp32_2432s028/main/key_derivation.c`](../firmware/cyd_esp32_2432s028/main/key_derivation.c)
(BIP-32/SLIP-0010 derivation for all 6 algorithms). Replace mbedtls/PSA
calls with the portable crypto from Phase 2.
2. Port [`firmware/cyd_esp32_2432s028/main/mnemonic.c`](../firmware/cyd_esp32_2432s028/main/mnemonic.c)
(BIP-39 wordlist + validation + mnemonic_to_seed via PBKDF2-HMAC-SHA512).
3. Port [`firmware/cyd_esp32_2432s028/main/bech32.c`](../firmware/cyd_esp32_2432s028/main/bech32.c)
(npub encoding).
### Phase 4: OTP pad from SDXC (the key feature)
1. **`otp_pad_sd.cpp`:**
- `otp_pad_init()`: mount the SD card via `sd.begin(SdioConfig(FIFO_SDIO))`,
open `/pad.bin` for reading, open `/pad.offset` for the persistent offset.
Read the offset file on boot; if absent, start at 0.
- `otp_pad_read(buf, len)`: seek to the current offset in `/pad.bin`, read
`len` bytes, advance the offset. If the offset + len exceeds the file
size, return an error (pad exhausted).
- `otp_pad_persist_offset()`: write the current offset to `/pad.offset`
after each `encrypt`/`decrypt` call (or batch: persist every N calls to
reduce SD wear).
- `otp_pad_zeroize()`: close files, zeroize in-RAM state.
2. **Wire into the `encrypt`/`decrypt` verbs:** replace the CYD's
HKDF-derived in-RAM pad with `otp_pad_read()`. The wire contract is
unchanged (`encrypt`/`decrypt` with `algorithm:"otp"`, base64 payload,
`pad_offset` in the response).
3. **UI:** show "reading pad from SD…" during the read (the SD read is fast
but the user should see activity). Show the pad offset + remaining bytes on
the idle screen.
### Phase 5: Transport + dispatch + UI
1. **Transport (`transport.cpp`):** TinyUSB CDC-ACM + 4-byte length-prefix
framing (same as [`firmware/cyd_esp32_2432s028/main/uart_transport.c`](../firmware/cyd_esp32_2432s028/main/uart_transport.c)).
Optionally add a WebUSB vendor interface for browser transport (the Teensy's
Hi-Speed USB makes this fast).
2. **Dispatch (`dispatch.cpp`):** port `handle_request()` from
[`firmware/cyd_esp32_2432s028/main/main.c`](../firmware/cyd_esp32_2432s028/main/main.c)
— the entire verb dispatch (all nostr_* + algorithm-based verbs, enforcement
matrix, structured results, auth envelope verify). This is the bulk of the
logic and ports nearly verbatim (only the crypto backend calls change).
3. **UI (`ui.cpp`):** port [`firmware/cyd_esp32_2432s028/main/ui.c`](../firmware/cyd_esp32_2432s028/main/ui.c)
(LVGL 8.3 or hand-rolled). The UI screens are: startup menu → generate
mnemonic → confirm mnemonic → enter mnemonic → idle (show npub + pad offset)
→ approval prompt. The Teensy's 600 MHz M7 makes LVGL snappy.
### Phase 6: Integration + testing
1. **End-to-end smoke test:** load a mnemonic, exercise every verb over USB
CDC with a Python script or the Web Serial test page
([`examples/cyd_webserial_demo.html`](../examples/cyd_webserial_demo.html)
works for any CDC device).
2. **OTP pad test:** place a known pad file on the 1 TB SDXC card, run
`encrypt` + `decrypt` round-trips, verify the XOR is correct and the offset
advances + persists across power cycles.
3. **Cross-board parity:** same mnemonic on the Teensy 4.1 and the CYD → same
npub, same secp256k1/ed25519/x25519/PQ public keys, same signatures.
4. **Performance:** measure SLH-DSA-128s sign time (expect ~1-2 s vs 5-30 s on
ESP32), ML-DSA-65 sign time (expect ~50 ms), SD pad read throughput.
## Open questions / decisions
- **UI framework:** LVGL 8.3 (heavier, proven on CYD) vs hand-rolled (lighter,
faster to port, no external dep). The Teensy has enough RAM for LVGL. **Lean
toward LVGL** for consistency with the CYD.
- **ed25519/x25519 impl:** mbedtls is available for Teensy via the
`mbedtls` Arduino library, but PSA crypto is not. Options: (a) mbedtls
ed25519 (if the Arduino mbedtls has it), (b) a portable ed25519 like
[`orlp/ed25519`](https://github.com/orlp/ed25519) + micro-ecc for x25519,
(c) libsodium for Teensy. **Investigate (a) first, fall back to (b).**
- **Toolchain:** Arduino CLI + Teensyduino (simplest, SdFat + USB stack
included) vs PlatformIO (better dependency management, CI-friendly). **Lean
toward Arduino CLI** for the initial port, migrate to PlatformIO later.
- **Offset persistence frequency:** writing `/pad.offset` to SD after every
`encrypt`/`decrypt` is safe but wears the SD. **Batch: persist every 16
calls, and also on idle timeout.** If power is lost, at most 16 pad bytes are
reused (acceptable for a signing device, not for a high-volume OTP channel).
## Verification
- `arduino-cli compile` (or `pio run`) builds clean for `teensy:avr:teensy41`.
- Flash to the Teensy 4.1, load a mnemonic, exercise every verb over USB CDC.
- 1 TB SDXC card mounts, `/pad.bin` reads at >20 MB/s, offset persists across
power cycles.
- Same mnemonic → same keys as the CYD and the host n_signer.
- SLH-DSA-128s signs in <3 s (vs 5-30 s on ESP32).