Files
n_signer/firmware/fpga_signer/README.md

193 lines
10 KiB
Markdown

# n_signer FPGA Signing Core
**Status:** Concept — brainstorming. No plan yet.
An FPGA-based secp256k1 signing core that provides the **maximum physical
security** possible for Nostr signing: constant-time crypto (no instruction
timing leakage), key material in FPGA fabric (no bus access to the key), and no
firmware (no malware injection surface). The FPGA is a **signing oracle** — a
small, auditable hardware module that does one thing (secp256k1 schnorr/ECDSA
signing) with side-channel resistance that software on an MCU cannot match.
## The secure element gap
Commercial secure elements (NXP JCOP, Infineon, Microchip ATECC) support NIST
curves (P-256, P-384) and RSA, but **not secp256k1** — the smart card industry
standardized on NIST curves, and secp256k1 was treated as a "Bitcoin curve"
that didn't get hardware support. This is why every Nostr/Bitcoin hardware
wallet (Coldcard, Ledger, Trezor, Keystone) uses a **general-purpose MCU**
running software secp256k1, not a secure element.
An FPGA fills this gap: it gives us **hardware-level secp256k1** without relying
on a secure element vendor to support the curve. We write the secp256k1 core
ourselves in Verilog, with full control over the timing, the key storage, and
the side-channel resistance.
## Why an FPGA
| Property | MCU (software) | FPGA (hardware) |
|---|---|---|
| Timing leakage | Branch prediction, cache, instruction timing | **None** — fixed datapath, every op takes the same cycles |
| Key storage | RAM (accessible via bus/debug) | **FPGA fabric / BRAM** (no external bus access) |
| Firmware attacks | OS, USB stack, BT stack = injection surface | **No firmware** — bitstream is the entire program |
| Debug access | JTAG/SWD can read RAM | **No debug path to key** if not routed |
| Auditability | Large codebase (thousands of lines of C) | **Small Verilog core** (~2000 lines, auditable) |
| PQ crypto | Yes (software) | No (too complex for FPGA) |
## Architecture: hybrid FPGA + MCU
The practical design is a **two-chip hybrid**: the FPGA is the signing oracle,
the MCU handles the protocol/UI/transport. The MCU sends a message hash + key
index to the FPGA over SPI; the FPGA signs with the key in fabric; the FPGA
returns the 64-byte signature. The private key never leaves the FPGA.
```mermaid
flowchart TD
subgraph Host_Side
Host[Host: laptop/phone<br/>n_signer client]
end
subgraph Signer_Device
MCU[MCU: RP2040 or nRF52840<br/>protocol + UI + transport<br/>PQ crypto in software]
FPGA[FPGA: iCE40-UP5K<br/>secp256k1 signing core<br/>ed25519 signing core<br/>SHA-256/512 cores<br/>key in BRAM]
Display[OLED / e-paper display]
Buttons[approve / deny buttons]
end
Host -->|USB / IR / NFC / BLE| MCU
MCU -->|SPI: msg_hash + key_index| FPGA
FPGA -->|SPI: 64-byte signature| MCU
MCU --> Display
Buttons --> MCU
MCU -->|response| Host
```
### Division of labor
| Function | Chip | Notes |
|---|---|---|
| Transport (USB/IR/NFC/BLE) | MCU | Ported from CYD/Teensy firmware |
| JSON-RPC dispatch | MCU | Ported from `handle_request()` |
| Auth envelope verify | MCU | secp256k1 schnorr verify (software) |
| Approval UI (display + buttons) | MCU | Ported from CYD UI |
| Mnemonic entry | MCU | BIP-39 wordlist + entry UI |
| BIP-32 / SLIP-0010 key derivation | **FPGA** | SHA-512 HMAC core + derivation FSM |
| secp256k1 schnorr sign | **FPGA** | Constant-time scalar multiply + schnorr |
| secp256k1 ECDSA sign | **FPGA** | Same scalar multiply + RFC 6979 nonce |
| ed25519 sign | **FPGA** | Curve25519 arithmetic core |
| x25519 ECDH | **FPGA** | Same curve as ed25519 |
| SHA-256 | **FPGA** | Hardware core (~1000 LUTs) |
| SHA-512 | **FPGA** | Hardware core (~2000 LUTs) — needed for BIP-32 |
| HMAC-SHA-256 | **FPGA** | SHA-256 core + FSM — for the `derive` verb |
| NIP-04 / NIP-44 encryption | MCU | AES + ChaCha20 in software |
| ML-DSA-65 / SLH-DSA-128s / ML-KEM-768 | MCU | PQClean in software (too complex for FPGA) |
| nostr_mine_event (PoW) | MCU | SHA-256 hash loop in software (or offload to FPGA) |
### Key storage in the FPGA
The mnemonic seed (64 bytes) is loaded into the FPGA's BRAM at boot (sent by
the MCU after the user enters the mnemonic). The FPGA derives secp256k1/ed25519/
x25519 keys on demand using its SHA-512 + modular arithmetic cores. The derived
private keys live in FPGA registers/BRAM and are **never readable from the SPI
interface** — the SPI interface only accepts "sign this hash with key index N"
commands and returns signatures. There is no "read key" command.
This is the key security property: **the private key is physically unreachable
from any external interface.** On an MCU, the key is in RAM and can be read via
JTAG/SWD or a firmware exploit. On the FPGA, the key is in fabric and there is
no path to it.
## FPGA board options
| Board | FPGA | LUTs | Toolchain | Price | Notes |
|---|---|---|---|---|---|
| **iCE40-UP5K** (e.g. iCEBreaker, Fomu) | iCE40UP5K | 5,300 | **Yosys + nextpnr** (open-source) | ~$15-20 | Best DIY choice. Open-source toolchain, DSP blocks, 128 KB BRAM, SPI flash. |
| **Gowin Tang Nano 9K** | GW1NR-9 | 8,640 | Yosys + nextpnr (open-source) | ~$8 | Cheapest. Newer open-source support. |
| **Lattice ECP5** (OrangeCrab, ULX3S) | LFE5U-12F / 25F / 45F | 12K-45K | Yosys + nextpnr (open-source) | ~$30-50 | More room. Supports **bitstream encryption** (important for production). |
| **Xilinx Artix-7** (Arty A7) | XC7A35T | 33,280 | Vivado (proprietary) | ~$100 | Professional. Overkill for a signer. |
**Recommendation: Lattice iCE40-UP5K for prototyping, ECP5 for production.**
The iCE40 has the most mature open-source toolchain (Yosys + nextpnr) and is
cheap. The ECP5 adds bitstream encryption (prevents bitstream cloning) for a
production device.
## The secp256k1 core (the hard part)
The secp256k1 signing core is the main development effort. It needs:
1. **256-bit modular arithmetic** over the secp256k1 prime field (p = 2²⁵⁶ - 2³² - 977):
- Modular add, subtract, multiply (Montgomery multiplication for performance)
- Modular inversion (Fermat's little theorem: a^(p-2) mod p, or extended Euclidean)
2. **Point operations** on the secp256k1 curve (y² = x³ + 7):
- Point addition (Jacobian coordinates)
- Point doubling
- Scalar multiplication (constant-time double-and-add, no conditional branches)
3. **Schnorr sign** (BIP-340):
- Deterministic nonce: k = HMAC-SHA256(d, x) where d is the key, x is the message hash
- R = k·G (point multiplication)
- e = tagged hash(R.x || P || m) (SHA-256)
- s = (k + e·x) mod n (scalar multiply + modular add)
- Signature = (R.x, s)
4. **ECDSA sign** (for the `scheme:"ecdsa"` option):
- RFC 6979 deterministic nonce: k = HMAC-SHA256(x, m) with rejection sampling
- R = k·G
- r = R.x mod n
- s = k⁻¹ · (m + r·x) mod n
- Signature = (r, s)
**Estimated size:** ~2000-5000 LUTs for the modular arithmetic + point
multiplication + schnorr/ECDSA FSM. Fits comfortably in an iCE40-UP5K (5,300
LUTs) alongside the SHA-256/512 cores and the SPI interface.
**Estimated sign time:** ~1-5 ms at 12 MHz (iCE40-UP5K typical clock). The
bottleneck is the 256-bit modular multiplication (~0.5-2 ms per multiply, ~256
multiplies per scalar multiplication). This is **much faster than software**
(the CYD's software schnorr sign takes ~10-50 ms).
## Open questions
- **Pure FPGA vs hybrid?** A pure-FPGA signer (no MCU) would implement the
entire dispatch + transport + UI in Verilog. This is extremely secure but
very hard to build (JSON parsing in Verilog is painful). The hybrid (FPGA
signing oracle + MCU protocol) is practical and still gives the key-isolation
benefit. **Lean toward hybrid.**
- **Which MCU?** RP2040 (cheap, no WiFi) or nRF52840 (low power, NFC, BLE)?
This determines the transport options (USB, IR, NFC, BLE).
- **Bitstream security:** iCE40 doesn't support bitstream encryption. If an
attacker reads the SPI flash, they get the bitstream (but not the key — the
key is loaded at runtime by the MCU, not stored in the bitstream). For
production, use ECP5 with bitstream encryption.
- **Key loading:** the MCU sends the mnemonic seed to the FPGA at boot over
SPI. Is this SPI transfer vulnerable to sniffing? It happens once, at boot,
inside the device. If the device is physically sealed, the SPI bus is not
accessible. For higher security, the FPGA could derive the key from the
mnemonic internally (the MCU sends the mnemonic string, the FPGA does
PBKDF2-HMAC-SHA512 + BIP-32 derivation in hardware).
- **ed25519 core:** worth implementing, or secp256k1-only? ed25519 is the same
field size (256-bit) but a different curve (Curve25519 vs secp256k1). The
modular arithmetic is similar but the curve operations differ. Adding
ed25519 roughly doubles the core size.
- **Open-source secp256k1 FPGA cores:** are there existing Verilog secp256k1
cores we can reuse or adapt? (There are Bitcoin mining cores, but those do
double-SHA256, not ECDSA. Academic ECDSA-on-FPGA papers exist but the code
is rarely open-sourced. We'd likely write the core from scratch.)
## Comparison to the other concepts
| | FPGA signer (hybrid) | MCU-only (CYD/Teensy) | Secure element |
|---|---|---|---|
| secp256k1 side-channel resistance | **Best** (constant-time, key in fabric) | Medium (software, timing leakage) | N/A (no secp256k1 support) |
| Key isolation | **Best** (no bus access to key) | Low (RAM, JTAG/SWD accessible) | Best (tamper-resistant) |
| Firmware attack surface | **Minimal** (no firmware on FPGA) | Large (OS, USB, BT stacks) | Minimal (fixed function) |
| PQ crypto | On MCU (software) | On MCU (software) | N/A |
| Development effort | **High** (Verilog secp256k1 core) | Low (port existing C code) | High (NDA + Java Card) |
| Cost | ~$15 FPGA + ~$4 MCU = ~$20 | ~$4-27 (MCU only) | ~$3-5 (chip only) |
| Auditability | **High** (small Verilog core, open toolchain) | Medium (large C codebase) | Low (proprietary, NDA) |
## Next steps
- Decide: hybrid (FPGA + MCU) vs pure FPGA
- Decide: iCE40-UP5K (prototype) vs ECP5 (production)
- Decide: secp256k1-only vs secp256k1 + ed25519
- Survey existing open-source secp256k1 / ECDSA FPGA cores
- Write a Verilog secp256k1 modular arithmetic core (the foundation)
- Write a plan (similar to [`plans/teensy41_signer_port.md`](../../plans/teensy41_signer_port.md))