Files
n_signer/firmware/fpga_signer

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.

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)