v0.1.0 - CYD firmware v0.0.2: algorithm-based API upgrade (all verbs, all algorithms), vendored Keccak/SHAKE + PSA ed25519/x25519 for IDF v5.4, Web Serial test page, CYD docs, Teensy 4.1 port plan (1TB SDXC OTP pad), brainstorming READMEs for BLE/IR/NFC/FPGA signer concepts

This commit is contained in:
Laan Tungir
2026-07-21 13:16:04 -04:00
parent ca18e1e42d
commit 64fbd5c874
23 changed files with 4271 additions and 488 deletions

View File

@@ -0,0 +1,248 @@
# 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).