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:
248
plans/teensy41_signer_port.md
Normal file
248
plans/teensy41_signer_port.md
Normal 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).
|
||||
Reference in New Issue
Block a user