diff --git a/examples/cyd_webserial_demo.html b/examples/cyd_webserial_demo.html new file mode 100644 index 0000000..3bf3e2a --- /dev/null +++ b/examples/cyd_webserial_demo.html @@ -0,0 +1,535 @@ + + +
+ + +Connect to the CYD (CH340 serial) via Web Serial, then exercise every algorithm and verb in the n_signer API. Chrome/Edge/Brave/Opera only.
+ +Slow on ESP32 — uses single-threaded PoW. Keep difficulty low.
+ + + + + + + + +OTP pad is derived from the mnemonic on the CYD. Offset advances monotonically.
+ + + + + + +` so the wire format is +visible. Reuse the feather demo's CSS (dark theme, cards, `.mono` log) for consistency. + +**Link it from [`firmware/README.md`](../firmware/README.md)** in the CYD section (the +"Quick validation" / Web Serial path), since the current README only points at the +feather WebUSB demo. + +### 16. Verification + +- `idf.py build` in [`firmware/cyd_esp32_2432s028`](../firmware/cyd_esp32_2432s028) compiles + clean. +- Flash to the connected CYD board (CH340 on `/dev/ttyUSB0`) and smoke-test each verb: + - Primary path: open [`examples/cyd_webserial_demo.html`](../examples/cyd_webserial_demo.html) + in Chrome/Edge, connect via Web Serial, exercise every card. + - Secondary path: a small Python script over `/dev/ttyUSB0` for headless confirmation. + - Cover: `get_public_key` (each algorithm), `sign`/`verify` (each sig alg), + `encapsulate`/`decapsulate`, `derive_shared_secret`, `derive`, + `nostr_get_public_key`, `nostr_sign_event`, `nostr_nip04_encrypt`/`decrypt`, + `nostr_nip44_encrypt`/`decrypt`, `nostr_mine_event` (low difficulty), + `encrypt`/`decrypt` (otp). + - Confirm `key_id` matches the first 16 hex of the returned pubkey for every alg. + - Confirm an invalid `(verb, algorithm)` pair returns code 1010. +- `grep -rn "sign_event\|nip04_encrypt\|nip04_decrypt\|nip44_encrypt\|nip44_decrypt" + firmware/cyd_esp32_2432s028/` returns no matches (legacy names gone). + +## Open questions / decisions baked in + +- **OTP pad source:** derived from mnemonic (no USB pad on CYD). Documented divergence. +- **`nostr_mine_event`:** implemented, single-threaded, hard 30 s default timeout, with a + "mining…" UI screen. Not stubbed — the main project has it and the user asked to bring + the firmware up to date. +- **No legacy-verb compat shim:** matches the main project's policy + ([`plans/legacy_verb_aliases.md`](../plans/legacy_verb_aliases.md) — "No backward- + compatibility shim is needed"). +- **feather_s3_tft not touched** in this pass. diff --git a/plans/teensy41_signer_port.md b/plans/teensy41_signer_port.md new file mode 100644 index 0000000..84c8d82 --- /dev/null +++ b/plans/teensy41_signer_port.md @@ -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
length-prefix framing] + Frame --> Auth[auth envelope verify
secp256k1 schnorr] + Auth -->|ok| Disp[dispatch.cpp
handle_request] + Auth -->|fail| Err[auth error] + Disp -->|nostr_*| NIP[Nostr verbs
secp256k1 NIP-06] + Disp -->|alg verb| Alg[Algorithm verbs
algorithm + index] + Alg -->|otp| OTP[otp_pad_sd.cpp
SDXC exFAT pad] + Alg -->|secp256k1/ed25519/x25519/PQ| KD[key_derivation.cpp] + OTP --> SD[(1 TB SDXC
exFAT
/pad.bin + /pad.offset)] + KD --> UI[ui.cpp
approval prompt
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). diff --git a/src/main.c b/src/main.c index ac8b47e..b306ae3 100644 --- a/src/main.c +++ b/src/main.c @@ -761,9 +761,9 @@ int socket_name_random(char *out, size_t out_len); /* Version information (auto-updated by build/version tooling) */ #define NSIGNER_VERSION_MAJOR 0 -#define NSIGNER_VERSION_MINOR 0 -#define NSIGNER_VERSION_PATCH 58 -#define NSIGNER_VERSION "v0.0.58" +#define NSIGNER_VERSION_MINOR 1 +#define NSIGNER_VERSION_PATCH 0 +#define NSIGNER_VERSION "v0.1.0" /* NSIGNER_HEADERLESS_DECLS_END */