6.9 KiB
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.rsandsrc/transport.rs: abstract Unix sockets,SO_PEERCRED, TCPsrc/secure_mem.rs:mlock/munlocksrc/miner.rs:std::thread,Mutexsrc/otp_pad.rs: reads the pad from a filesrc/tui.rs: ratatui/crossterm,libc::localtime_rsrc/http.rs,src/socket_name.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-Rustk256for firmware.serde_json: can run with onlyalloc. Considerserde-json-coreif 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
- Post-quantum memory use. RustCrypto
ml-dsa0.1 andslh-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. - Hardware support code. ESP-IDF TinyUSB composite devices (serial + WebUSB), the display drivers and touch input have to be recreated or wrapped.
esp-idf-svclessens this by keeping the ESP-IDF drivers underneath. - Toolchain. Xtensa needs the
espupfork of the compiler. RP2040 and i.MX RT use standard Rust. - 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
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 microcontrollerSecureBuffer:mlockon Linux, a zeroized static region on the microcontrollerClock: wall-clock time or a monotonic counter (policy windows, eventcreated_at)PadStorage: file on Linux, SD card or pad derived from the mnemonic on firmwareUserConfirm: TUI prompt on Linux, buttons or touch on firmwareTransport: framing of JSON-RPC over whatever byte stream the platform has (Unix socket, USB serial, UART, BLE)
Plan
- Decide scope. Pilot one board in Rust; the C firmware stays in use.
- No-std build check. A throwaway crate that depends on all the crypto crates with
default-features = false, built forthumbv6m-none-eabi(RP2040) andxtensa-esp32s3-espidf/xtensa-esp32s3-none-elf. - 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.shandpq_profile_results.json. - Workspace split. Create a
signer-corecrate (no-std + alloc) holding the portable modules listed above. - Platform interfaces. Define the interfaces above in
signer-core, with Linux versions insigner-linux. - nostr-core without the standard library. Add a
stdfeature (on by default) and makechrono/tracingoptional, or move only whatsigner-coreneeds. - Linux shell. Reduce the current binary to
signer-linux: sockets,mlock, threads, TUI and client, all on top ofsigner-core. - 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. - Feather S3 TFT pilot. Use
esp-idf-svc: USB CDC JSON-RPC, the TFT throughmipidsi+embedded-graphics, and buttons for confirmation. - Parity check. Run
usb-test.htmlandexamples/feather_*.pyagainst the Rust firmware. - Go/no-go review. Binary size, RAM headroom, signing latency, developer experience.
- Remaining boards (if go). CYD (
esp-idf-svc), then KB2040 (embassy-rp, no standard library), then Teensy 4.1 (imxrt-hal, least mature). - 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.