99 lines
6.9 KiB
Markdown
99 lines
6.9 KiB
Markdown
# Rust Firmware Unification Plan
|
|
|
|
Status: **proposal, not started**. The C `n_signer` firmware stays in use until each Rust board matches it.
|
|
|
|
## Question
|
|
|
|
Should the microcontroller firmware in `~/lt/n_signer/firmware/` be ported to this Rust project, so there is only one implementation?
|
|
|
|
## Short answer
|
|
|
|
Yes, but gradually, and only after checking the risks below. Rust works well on every chip the project uses. The open questions are how much memory the post-quantum crypto needs and how much work the hardware support code takes, not whether the language fits.
|
|
|
|
## Current firmware inventory (C, `n_signer/firmware/`)
|
|
|
|
| Target | MCU | Stack | State |
|
|
|---|---|---|---|
|
|
| `feather_s3_tft` | ESP32-S3 | ESP-IDF, TinyUSB (serial + WebUSB), TFT | Working |
|
|
| `cyd_esp32_2432s028` | ESP32 (Xtensa), CH340 serial chip | ESP-IDF, touch display | Working |
|
|
| `teensy41` | NXP i.MX RT1062 | Arduino/Teensyduino, SD card pad | Working |
|
|
| `kb2040_hidden_signer` | RP2040 | Arduino | Working |
|
|
| `kb2040_dual_test` | RP2040 | Arduino | Test |
|
|
| `nfc_card_signer`, `ble_wearable_signer`, `ir_airgap_signer`, `fpga_signer` | — | — | README only |
|
|
|
|
The post-quantum code on the ESP32 boards is PQClean with an mbedtls/Keccak backend (`resources/pqclean/common/crypto_backend_mbedtls.c`).
|
|
|
|
## Rust on microcontrollers: maturity by target
|
|
|
|
| Target | Rust option | Notes |
|
|
|---|---|---|
|
|
| ESP32-S3 / ESP32 | `esp-idf-svc` (full standard library on ESP-IDF) or `esp-hal` (bare metal) | Backed by Espressif. Xtensa chips need the `espup` toolchain; RISC-V ESP32 chips (C3/C6/H2) use normal Rust. |
|
|
| RP2040 | `embassy-rp`, `rp2040-hal` | Excellent. USB CDC is supported through `embassy-usb` / `usb-device`. |
|
|
| Teensy 4.1 | `imxrt-hal`, `teensy4-bsp` | Usable, but a smaller community. |
|
|
| FPGA | Only on a RISC-V soft core | Rust does not apply to the logic itself. |
|
|
|
|
## How portable this codebase is
|
|
|
|
Portable logic (should build without the standard library, using `alloc`): `dispatcher`, `role_table`, `selector`, `enforcement`, `key_store`, `alg_cache`, `pq_crypto`, `pq_drbg`, `mnemonic`, `auth_envelope`, the cipher logic in `otp_pad`, and `error`.
|
|
|
|
Linux-only parts that would stay in the Linux binary:
|
|
- [`src/server.rs`](../src/server.rs) and [`src/transport.rs`](../src/transport.rs): abstract Unix sockets, `SO_PEERCRED`, TCP
|
|
- [`src/secure_mem.rs`](../src/secure_mem.rs): `mlock`/`munlock`
|
|
- [`src/miner.rs`](../src/miner.rs): `std::thread`, `Mutex`
|
|
- [`src/otp_pad.rs`](../src/otp_pad.rs): reads the pad from a file
|
|
- [`src/tui.rs`](../src/tui.rs): ratatui/crossterm, `libc::localtime_r`
|
|
- [`src/http.rs`](../src/http.rs), [`src/socket_name.rs`](../src/socket_name.rs), [`src/main.rs`](../src/main.rs), `src/client/`
|
|
|
|
Dependencies:
|
|
- `sha2`, `hmac`, `sha3`, `chacha20poly1305`, `ed25519-dalek`, `x25519-dalek`, `ml-kem`, `ml-dsa`, `slh-dsa`: all can build without the standard library (turn off default features).
|
|
- `secp256k1`: wraps a C library. It can run on microcontrollers but needs a C cross-compiler; consider pure-Rust `k256` for firmware.
|
|
- `serde_json`: can run with only `alloc`. Consider `serde-json-core` if heap is tight.
|
|
- `nostr-core` (`../nostr_core_lib_rust/core`): currently needs the standard library (`chrono`, `tracing`, `rand`). It needs a no-std feature.
|
|
|
|
## Risks
|
|
|
|
1. **Post-quantum memory use.** RustCrypto `ml-dsa` 0.1 and `slh-dsa` (release candidate) are young, and their stack use has not been measured. This is critical on the RP2040 (264 KB of RAM). Measure before committing.
|
|
2. **Hardware support code.** ESP-IDF TinyUSB composite devices (serial + WebUSB), the display drivers and touch input have to be recreated or wrapped. `esp-idf-svc` lessens this by keeping the ESP-IDF drivers underneath.
|
|
3. **Toolchain.** Xtensa needs the `espup` fork of the compiler. RP2040 and i.MX RT use standard Rust.
|
|
4. **Different code on each side of the wire.** Keeping separate C and Rust implementations is harder to keep in step, but it also means one bug does not hit every device. The conformance vectors (step 8) help either way.
|
|
|
|
## Target architecture
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[signer-core no_std + alloc] --> B[signer-linux]
|
|
A --> C[fw-feather-s3 esp-idf-svc]
|
|
A --> D[fw-cyd esp-idf-svc]
|
|
A --> E[fw-kb2040 embassy-rp]
|
|
A --> F[fw-teensy41 imxrt-hal]
|
|
G[C n_signer firmware] -.retired per board after parity.-> C
|
|
```
|
|
|
|
Platform interfaces in `signer-core`:
|
|
- `Rng`: OS random source on Linux, hardware RNG on the microcontroller
|
|
- `SecureBuffer`: `mlock` on Linux, a zeroized static region on the microcontroller
|
|
- `Clock`: wall-clock time or a monotonic counter (policy windows, event `created_at`)
|
|
- `PadStorage`: file on Linux, SD card or pad derived from the mnemonic on firmware
|
|
- `UserConfirm`: TUI prompt on Linux, buttons or touch on firmware
|
|
- `Transport`: framing of JSON-RPC over whatever byte stream the platform has (Unix socket, USB serial, UART, BLE)
|
|
|
|
## Plan
|
|
|
|
1. **Decide scope.** Pilot one board in Rust; the C firmware stays in use.
|
|
2. **No-std build check.** A throwaway crate that depends on all the crypto crates with `default-features = false`, built for `thumbv6m-none-eabi` (RP2040) and `xtensa-esp32s3-espidf` / `xtensa-esp32s3-none-elf`.
|
|
3. **Memory profiling.** Peak stack and heap for ML-DSA-65 keygen/sign and SLH-DSA-128s sign on real hardware. Compare with the C numbers from `firmware/teensy41/check_stack.sh` and `pq_profile_results.json`.
|
|
4. **Workspace split.** Create a `signer-core` crate (no-std + alloc) holding the portable modules listed above.
|
|
5. **Platform interfaces.** Define the interfaces above in `signer-core`, with Linux versions in `signer-linux`.
|
|
6. **nostr-core without the standard library.** Add a `std` feature (on by default) and make `chrono`/`tracing` optional, or move only what `signer-core` needs.
|
|
7. **Linux shell.** Reduce the current binary to `signer-linux`: sockets, `mlock`, threads, TUI and client, all on top of `signer-core`.
|
|
8. **Conformance vectors.** Shared test vectors: the same mnemonic gives identical public keys and signatures in the C firmware, Rust Linux and Rust firmware. Schnorr signatures need fixed auxiliary randomness. Extend `tests/pq_conformance.rs`.
|
|
9. **Feather S3 TFT pilot.** Use `esp-idf-svc`: USB CDC JSON-RPC, the TFT through `mipidsi` + `embedded-graphics`, and buttons for confirmation.
|
|
10. **Parity check.** Run `usb-test.html` and `examples/feather_*.py` against the Rust firmware.
|
|
11. **Go/no-go review.** Binary size, RAM headroom, signing latency, developer experience.
|
|
12. **Remaining boards (if go).** CYD (`esp-idf-svc`), then KB2040 (`embassy-rp`, no standard library), then Teensy 4.1 (`imxrt-hal`, least mature).
|
|
13. **New targets and retirement.** Start NFC/BLE/IR/FPGA-soft-core in Rust. Retire each C firmware only when its Rust version matches it.
|
|
|
|
## Fallback if the go/no-go fails
|
|
|
|
Keep the C firmware for microcontrollers. Treat the JSON-RPC API (`n_signer/api.md`) and the conformance vectors from step 8 as the shared contract between the two codebases.
|