v0.0.48 - Added OTP one-time pad encryption (otp_encrypt/otp_decrypt verbs), HTTP listener mode (--listen http:HOST:PORT), interactive OTP pad auto-scan on USB drives, raised SERVER_MAX_MSG_SIZE to 16MB, updated README with curl examples and current API documentation
This commit is contained in:
176
README.md
176
README.md
@@ -20,7 +20,8 @@ This is a **program, not a daemon**:
|
||||
- purpose/curve enforcement
|
||||
- request dispatch
|
||||
- interactive terminal UI
|
||||
- transport adapter(s)
|
||||
- transport adapter(s) (Unix socket, qrexec, FIPS/TCP, HTTP)
|
||||
- OTP one-time pad encryption (optional, with USB pad)
|
||||
|
||||
You run it when you need signing. You stop it when you are done. Closing the terminal or quitting the program ends the trust session and destroys state.
|
||||
|
||||
@@ -74,9 +75,16 @@ When started, `n_signer` immediately enters terminal input mode:
|
||||
- On `E`: prompt for mnemonic with terminal echo disabled, then validate.
|
||||
- On `G`: generate a fresh 12-word BIP-39 mnemonic from `getrandom(2)`, display it numbered with a "WRITE THIS DOWN — IT WILL NOT BE SHOWN AGAIN" warning, then continue. There is no confirmation step.
|
||||
2. Build in-memory role/selector state from the mnemonic.
|
||||
3. Pick the abstract socket name (random BIP-39 pair, or `--socket-name` / `--name` / `-n` override).
|
||||
4. Initialize transport endpoints and bind the socket.
|
||||
5. Switch to running status display, with the signer name and socket address shown in the banner.
|
||||
3. **Interactive transport selection** (if no `--listen` flag given and stdin is a TTY): choose one or more of:
|
||||
- Local Unix socket
|
||||
- Qubes qrexec bridge
|
||||
- FIPS/TCP listener (framed JSON)
|
||||
- HTTP listener (curl-friendly)
|
||||
4. **Index whitelist** (optional): restrict which `nostr_index` values this session can access.
|
||||
5. **OTP pad selection** (optional): auto-scans attached USB drives for OTP pads and offers to bind one. See [`plans/otp_nostr_integration.md`](plans/otp_nostr_integration.md).
|
||||
6. Pick the abstract socket name (random BIP-39 pair, or `--socket-name` / `--name` / `-n` override).
|
||||
7. Initialize transport endpoints and bind the socket.
|
||||
8. Switch to running status display, with the signer name, socket address, and OTP pad status shown in the banner.
|
||||
|
||||
No startup files are read or written. The mnemonic — typed or generated — lives only in `mlock`'d memory and is zeroized on shutdown or crash.
|
||||
|
||||
@@ -93,7 +101,15 @@ After unlock, terminal becomes a live status and control console. Example layout
|
||||
|
||||
```text
|
||||
n_signer v0.x | foreground session active
|
||||
transport: unix-abstract:@nsigner
|
||||
|
||||
Connections
|
||||
Server: unix @nsigner (bridge-source-trusted)
|
||||
Client: nsigner --socket-name nsigner client '<json>'
|
||||
Qrexec: qrexec-client-vm <target_qube> qubes.NsignerRpc
|
||||
Server: HTTP 127.0.0.1:11111
|
||||
Client: curl -X POST http://127.0.0.1:11111/ -H 'Content-Type: application/json' -d '<json>'
|
||||
OTP pad: 333e9902db839d9d... (offset 288 / 1048576 bytes)
|
||||
|
||||
session: unlocked (RAM-only)
|
||||
|
||||
Roles
|
||||
@@ -277,11 +293,11 @@ Response: `{"id":"3","result":{"algorithm":"ml-dsa-65","public_key":"<hex>","key
|
||||
{"id":"6","method":"derive_shared_secret","params":["<peer_pubkey_hex>",{"algorithm":"x25519","index":0}]}
|
||||
```
|
||||
|
||||
### 4c.4 Old verb aliases
|
||||
### 4c.4 Verb aliases
|
||||
|
||||
Old verb names map to the new algorithm-based verbs when used with the `algorithm` parameter:
|
||||
These verb aliases are also available. When used with the `algorithm` parameter, they map to the algorithm-based verbs:
|
||||
|
||||
| Old verb | New verb | Default algorithm |
|
||||
| Alias | Canonical verb | Default algorithm |
|
||||
|---|---|---|
|
||||
| `sign_data` | `sign` | (from `algorithm` parameter) |
|
||||
| `ssh_sign` | `sign` | `ed25519` |
|
||||
@@ -289,7 +305,7 @@ Old verb names map to the new algorithm-based verbs when used with the `algorith
|
||||
| `kem_encapsulate` | `encapsulate` | `ml-kem-768` |
|
||||
| `kem_decapsulate` | `decapsulate` | `ml-kem-768` |
|
||||
|
||||
Without the `algorithm` parameter, old verbs fall through to the role-based path (backward compatible).
|
||||
Without the `algorithm` parameter, these aliases fall through to the role-based path (backward compatible).
|
||||
|
||||
### 4c.5 Algorithm-based preapprove
|
||||
|
||||
@@ -322,7 +338,7 @@ Request shape is JSON-RPC with NIP-46-style methods and optional trailing select
|
||||
|
||||
Implemented signer verbs in this build:
|
||||
|
||||
**Nostr verbs** (require `purpose="nostr"`, `curve="secp256k1"`):
|
||||
**Nostr verbs** (secp256k1 only, use `role` or `nostr_index` selector):
|
||||
|
||||
- `get_public_key`
|
||||
- `sign_event`
|
||||
@@ -330,27 +346,36 @@ Implemented signer verbs in this build:
|
||||
- `nip44_encrypt` / `nip44_decrypt`
|
||||
- `mine_event` — add NIP-13 proof-of-work and sign (see below)
|
||||
|
||||
**Multi-algorithm verbs**:
|
||||
**Algorithm-based verbs** (use `algorithm` + `index` selector, no role needed):
|
||||
|
||||
| Verb | Required purpose | Required curve | Description |
|
||||
|---|---|---|---|
|
||||
| `get_public_key` | any | any | Returns the role's public key. secp256k1 returns plain hex; other algorithms return structured JSON (see [§4b.4](#4b4-structured-get_public_key-response)). |
|
||||
| `sign_data` | `ssh` or `pq-sig` | `ed25519`, `ml-dsa-65`, or `slh-dsa-128s` | Sign arbitrary bytes (not a Nostr event). Returns `{"signature":"<hex>","algorithm":"<alg>"}`. |
|
||||
| `verify_signature` | `ssh` or `pq-sig` | `ed25519`, `ml-dsa-65`, or `slh-dsa-128s` | Verify a signature. Returns `{"valid":true/false}`. |
|
||||
| `ssh_sign` | `ssh` | `ed25519` | Sign an SSH authentication challenge in ed25519 format. Returns `{"signature":"<hex>","algorithm":"ed25519"}`. |
|
||||
| `kem_encapsulate` | `pq-kem` | `ml-kem-768` | Encapsulate against a peer's ML-KEM-768 public key. Returns `{"ciphertext":"<hex>","shared_secret":"<hex>","algorithm":"ml-kem-768"}`. |
|
||||
| `kem_decapsulate` | `pq-kem` | `ml-kem-768` | Decapsulate a ciphertext using the role's ML-KEM-768 private key. Returns `{"shared_secret":"<hex>","algorithm":"ml-kem-768"}`. |
|
||||
| Verb | Valid algorithms | Description |
|
||||
|---|---|---|
|
||||
| `sign` | secp256k1, ed25519, ml-dsa-65, slh-dsa-128s | Sign arbitrary bytes. Returns `{"signature":"<hex>","algorithm":"<alg>","key_id":"<16hex>"}`. |
|
||||
| `verify` | secp256k1, ed25519, ml-dsa-65, slh-dsa-128s | Verify a signature. Returns `{"valid":true/false}`. |
|
||||
| `encapsulate` | ml-kem-768 | Encapsulate against a peer's ML-KEM-768 public key. Returns `{"ciphertext":"<hex>","shared_secret":"<hex>","algorithm":"ml-kem-768"}`. |
|
||||
| `decapsulate` | ml-kem-768 | Decapsulate a ciphertext using the derived ML-KEM-768 private key. Returns `{"shared_secret":"<hex>","algorithm":"ml-kem-768"}`. |
|
||||
| `derive_shared_secret` | x25519 | ECDH key agreement. Returns `{"shared_secret":"<hex>","algorithm":"x25519"}`. |
|
||||
| `get_public_key` | all algorithms | Returns the public key for the specified algorithm+index. |
|
||||
|
||||
Example `sign_data` request:
|
||||
**OTP verbs** (one-time pad encryption, requires `--otp-pad-dir` + `--otp-pad`):
|
||||
|
||||
| Verb | Description |
|
||||
|---|---|
|
||||
| `otp_encrypt` | Encrypt plaintext (base64) with the bound OTP pad. Returns ASCII-armored or binary ciphertext. |
|
||||
| `otp_decrypt` | Decrypt ciphertext with the bound OTP pad. Returns plaintext (base64). |
|
||||
|
||||
See [`plans/otp_nostr_integration.md`](plans/otp_nostr_integration.md) for the full OTP design.
|
||||
|
||||
Example `sign` request (algorithm-based):
|
||||
|
||||
```jsonc
|
||||
{ "id": "6", "method": "sign_data", "params": ["<message_hex>", { "role": "pq-ml-dsa" }] }
|
||||
{ "id": "6", "method": "sign", "params": ["<message_hex>", { "algorithm": "ed25519", "index": 0 }] }
|
||||
```
|
||||
|
||||
Example `kem_encapsulate` request:
|
||||
Example `otp_encrypt` request:
|
||||
|
||||
```jsonc
|
||||
{ "id": "7", "method": "kem_encapsulate", "params": ["<peer_pubkey_hex>", { "role": "pq-kem-main" }] }
|
||||
{ "id": "7", "method": "otp_encrypt", "params": ["<plaintext_base64>", { "encoding": "ascii" }] }
|
||||
```
|
||||
|
||||
### `mine_event` — NIP-13 Proof-of-Work
|
||||
@@ -409,11 +434,13 @@ Representative error codes:
|
||||
- `approval_denied`
|
||||
- `internal_error`
|
||||
|
||||
## 6. Purpose and curve enforcement
|
||||
## 6. Purpose and curve enforcement (role-based API)
|
||||
|
||||
Selector resolution chooses *which* role. Enforcement decides *whether the requested method is valid* for that role.
|
||||
|
||||
### 6.1 Purpose values
|
||||
**Note:** The algorithm-based API (§4c) bypasses purpose/curve enforcement entirely — the caller specifies the algorithm directly, and the dispatcher derives the key on demand. Purpose values are only used by the role-based API for enforcement.
|
||||
|
||||
### 6.1 Purpose values (role-based API only)
|
||||
|
||||
| Purpose | Description |
|
||||
|---|---|
|
||||
@@ -436,7 +463,7 @@ Selector resolution chooses *which* role. Enforcement decides *whether the reque
|
||||
| `slh-dsa-128s` | SLH-DSA-128s (FIPS 205, hash-based PQ signatures) |
|
||||
| `ml-kem-768` | ML-KEM-768 (FIPS 203, lattice-based PQ KEM) |
|
||||
|
||||
### 6.3 Enforcement matrix
|
||||
### 6.3 Enforcement matrix (role-based API)
|
||||
|
||||
| Verb | Required purpose | Required curve |
|
||||
|---|---|---|
|
||||
@@ -445,18 +472,16 @@ Selector resolution chooses *which* role. Enforcement decides *whether the reque
|
||||
| `nip04_encrypt` / `nip04_decrypt` | `nostr` | `secp256k1` |
|
||||
| `nip44_encrypt` / `nip44_decrypt` | `nostr` | `secp256k1` |
|
||||
| `get_public_key` | any | any (must match role's declared curve) |
|
||||
| `sign_data` | `ssh` or `pq-sig` | `ed25519`, `ml-dsa-65`, or `slh-dsa-128s` |
|
||||
| `sign_data` / `ssh_sign` | `ssh` or `pq-sig` | `ed25519`, `ml-dsa-65`, or `slh-dsa-128s` |
|
||||
| `verify_signature` | `ssh` or `pq-sig` | `ed25519`, `ml-dsa-65`, or `slh-dsa-128s` |
|
||||
| `ssh_sign` | `ssh` | `ed25519` |
|
||||
| `kem_encapsulate` | `pq-kem` | `ml-kem-768` |
|
||||
| `kem_decapsulate` | `pq-kem` | `ml-kem-768` |
|
||||
| `kem_encapsulate` / `kem_decapsulate` | `pq-kem` | `ml-kem-768` |
|
||||
|
||||
Example:
|
||||
The algorithm-based API (§4c) does not use this matrix — the caller specifies the algorithm directly, and enforcement is based on the verb+algorithm combination (see [§4c.6](#4c6-enforcement-matrix)).
|
||||
|
||||
Example (role-based):
|
||||
|
||||
- `sign_event` requires `purpose="nostr"` and `curve="secp256k1"`.
|
||||
- `mine_event` requires `purpose="nostr"` and `curve="secp256k1"` (same as `sign_event`).
|
||||
- If caller selects a Bitcoin-role key for `sign_event`, request fails with `purpose_mismatch`.
|
||||
- If caller selects a `pq-sig`/`ml-dsa-65` key for `sign_event`, request fails with `purpose_mismatch`.
|
||||
|
||||
This prevents cross-protocol misuse inside one mnemonic-rooted signer process. Fail-closed: any unlisted `(verb, purpose, curve)` combination is rejected.
|
||||
|
||||
@@ -487,10 +512,55 @@ Discovery:
|
||||
- `nsigner list` enumerates currently bound `nsigner_*` abstract sockets by reading `/proc/net/unix`.
|
||||
- `nsigner --listen stdio` runs one framed JSON-RPC request/response over stdin/stdout.
|
||||
- `nsigner --listen qrexec` is the same stdio framing mode, but caller identity can be derived from `QREXEC_REMOTE_DOMAIN` (displayed as `qubes:<source-vm>`).
|
||||
- `nsigner --listen tcp:IPv4:PORT` or `tcp:[IPv6]:PORT` enables TCP listening for non-AF_UNIX clients (for example `tcp:127.0.0.1:8080`, `tcp:[::]:8080`, or `tcp:[fd00::1234]:8080`).
|
||||
- `nsigner --listen tcp:IPv4:PORT` or `tcp:[IPv6]:PORT` enables FIPS/TCP listening for non-AF_UNIX clients (for example `tcp:127.0.0.1:11111`, `tcp:[::]:11111`, or `tcp:[fd00::1234]:11111`). Uses framed JSON protocol (not HTTP).
|
||||
- `nsigner --listen http:HOST:PORT` enables HTTP listening for curl-friendly access (for example `http:127.0.0.1:11111`). Uses standard HTTP POST with JSON body — no custom framing. CORS headers included for browser access. Defaults to localhost; pass `http:0.0.0.0:PORT` to expose externally.
|
||||
- `nsigner bridge --to <socket-name>` is a stateless relay for Qubes qrexec: reads one framed request from stdin, forwards it to a persistent signer's abstract unix socket, and relays the response to stdout. Used as the `qubes.NsignerRpc` service entrypoint. See [§8.3](#83-qubes-os-qube) and [`plans/qrexec_persistent_bridge.md`](plans/qrexec_persistent_bridge.md).
|
||||
- `--bridge-source-trusted` (unix listener only): marks the socket as a trusted bridge endpoint. Each connection sends a framed `{"qrexec_source":"<vm>"}` preamble before the request, and the caller identity is composed as `qubes:<vm>` — matching the native qrexec identity path. This enables a persistent signer (mnemonic in mlock'd RAM) to receive qrexec-routed requests without spawning a fresh process per call.
|
||||
|
||||
#### curl examples (HTTP mode)
|
||||
|
||||
Start the signer in HTTP mode:
|
||||
```bash
|
||||
nsigner --listen http:127.0.0.1:11111 --allow-all
|
||||
```
|
||||
|
||||
Get a public key:
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:11111/ -H 'Content-Type: application/json' \
|
||||
-d '{"id":"1","method":"get_public_key","params":[{"role":"main"}]}'
|
||||
```
|
||||
|
||||
Sign a Nostr event:
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:11111/ -H 'Content-Type: application/json' \
|
||||
-d '{"id":"1","method":"sign_event","params":[{"pubkey":"...","created_at":1234567890,"kind":1,"tags":[],"content":"hello"}]}'
|
||||
```
|
||||
|
||||
OTP encrypt (requires `--otp-pad-dir` and `--otp-pad`):
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:11111/ -H 'Content-Type: application/json' \
|
||||
-d '{"id":"1","method":"otp_encrypt","params":["SGVsbG8sIE9UUCB3b3JsZCE=",{"encoding":"ascii"}]}'
|
||||
```
|
||||
|
||||
OTP decrypt:
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:11111/ -H 'Content-Type: application/json' \
|
||||
-d '{"id":"1","method":"otp_decrypt","params":["-----BEGIN OTP MESSAGE-----...","{"encoding":"ascii"}]}'
|
||||
```
|
||||
|
||||
#### Unix socket examples (framed mode)
|
||||
|
||||
```bash
|
||||
# Get public key
|
||||
nsigner --socket-name nsigner client '{"id":"1","method":"get_public_key","params":[{"role":"main"}]}'
|
||||
|
||||
# Sign event
|
||||
nsigner --socket-name nsigner client '{"id":"1","method":"sign_event","params":[{"pubkey":"...","created_at":1234567890,"kind":1,"tags":[],"content":"hello"}]}'
|
||||
|
||||
# OTP encrypt
|
||||
nsigner --socket-name nsigner client '{"id":"1","method":"otp_encrypt","params":["SGVsbG8=",{"encoding":"ascii"}]}'
|
||||
```
|
||||
|
||||
### 7.2 ESP32 MCU: TinyUSB composite (CDC + WebUSB)
|
||||
|
||||
On Feather S3 TFT, MCU targets run the same core signer modules behind a TinyUSB composite transport:
|
||||
@@ -528,9 +598,11 @@ MCU target reuses mnemonic/role/selector/enforcement/dispatcher core and swaps t
|
||||
|
||||
Qubes deployment runs `n_signer` in a dedicated signer qube (e.g. `nostr_signer`) as a foreground process under explicit user session control. The mnemonic lives only in mlock'd RAM in that qube — a compromised agent in a caller qube cannot read it (hypervisor-enforced memory isolation).
|
||||
|
||||
Two transport paths are supported:
|
||||
Three transport paths are supported:
|
||||
|
||||
**FIPS/TCP** — the signer listens on `tcp:[::]:8080` and FIPS carries traffic between qubes as an IPv6 mesh substrate. See [`documents/FIPS_DEPLOYMENT.md`](documents/FIPS_DEPLOYMENT.md).
|
||||
**FIPS/TCP** — the signer listens on `tcp:[::]:11111` and FIPS carries traffic between qubes as an IPv6 mesh substrate. Uses framed JSON protocol. See [`documents/FIPS_DEPLOYMENT.md`](documents/FIPS_DEPLOYMENT.md).
|
||||
|
||||
**HTTP** — the signer listens on `http:127.0.0.1:11111` for curl-friendly access within the same qube. Uses standard HTTP POST with JSON body. No auth envelopes required (relies on localhost binding + policy/approval prompts). Add `--listen http:127.0.0.1:11111` or select option 4 in the interactive transport menu.
|
||||
|
||||
**Qubes qrexec bridge** (recommended for no-network deployments) — a persistent signer listens on an abstract unix socket, and a stateless `nsigner bridge` relay (the `qubes.NsignerRpc` qrexec service) forwards one request per qrexec invocation. No network, no FIPS — pure intra-host IPC. Caller identity is `qubes:<source-vm>` (from `QREXEC_REMOTE_DOMAIN`), relayed via a trusted preamble. See [`plans/qrexec_persistent_bridge.md`](plans/qrexec_persistent_bridge.md) for the full design.
|
||||
|
||||
@@ -592,10 +664,22 @@ Generic stdio transport mode (single framed request over stdin/stdout):
|
||||
nsigner --listen stdio
|
||||
```
|
||||
|
||||
TCP transport mode (no TUI; serves requests until terminated):
|
||||
FIPS/TCP transport mode (framed JSON, no TUI; serves requests until terminated):
|
||||
|
||||
```bash
|
||||
nsigner --listen tcp:[::]:8080
|
||||
nsigner --listen tcp:[::]:11111
|
||||
```
|
||||
|
||||
HTTP transport mode (curl-friendly, no TUI; serves requests until terminated):
|
||||
|
||||
```bash
|
||||
nsigner --listen http:127.0.0.1:11111
|
||||
```
|
||||
|
||||
With OTP pad bound (auto-detects pads on USB drives in interactive mode):
|
||||
|
||||
```bash
|
||||
nsigner --listen http:127.0.0.1:11111 --otp-pad-dir /media/user/Music/pads --otp-pad 333e9902db839d9d --allow-all
|
||||
```
|
||||
|
||||
Qrexec bridge mode (stateless relay to a persistent signer's unix socket; used as the `qubes.NsignerRpc` service):
|
||||
@@ -687,15 +771,19 @@ Static build:
|
||||
|
||||
## 11. Implemented adjuncts and future work
|
||||
|
||||
### Implemented PoC
|
||||
### Implemented features
|
||||
|
||||
- **HTTP listener mode (`--listen http:HOST:PORT`).** Curl-friendly HTTP POST with JSON body. CORS headers included. See [§7.1](#71-linux-desktop-abstract-namespace-unix-socket) for curl examples.
|
||||
- **OTP one-time pad encryption (`otp_encrypt` / `otp_decrypt` verbs).** Information-theoretically secure encryption using one-time pads from the [`otp`](../otp) project. Pad material on USB drive, auto-detected at startup. See [`plans/otp_nostr_integration.md`](plans/otp_nostr_integration.md).
|
||||
- **Algorithm-based API (`sign`, `verify`, `encapsulate`, `decapsulate`, `derive_shared_secret`).** Caller specifies algorithm + index, no role needed. See [§4c](#4c-algorithm-based-api).
|
||||
- **MCU / USB signer (Feather ESP32-S3 Reverse TFT).** Working PoC in [`firmware/feather_s3_tft`](firmware/feather_s3_tft). Single TinyUSB composite USB device exposes both CDC-ACM and WebUSB Vendor interfaces. Same dispatcher serves both transports with auth envelope verification, on-device TFT prompts, and physical button approval. See [`firmware/README.md`](firmware/README.md) and [`plans/feather_tinyusb_composite.md`](plans/feather_tinyusb_composite.md).
|
||||
|
||||
### Future work (deferred)
|
||||
|
||||
These items are designed and worth doing, but are not in the current implementation scope. They are listed here so they are not lost.
|
||||
|
||||
- **`--listen http:[addr]:port` mode.** Today the TCP listener speaks 4-byte big-endian length-prefixed framed JSON-RPC, which is correct for low-overhead local IPC but is not directly reachable from web browsers (`fetch`, `XMLHttpRequest`, `curl`). A small additional listener that wraps the same dispatcher in minimal HTTP/1.1 (`POST /rpc`, `Content-Type: application/json`, `Content-Length`-framed body, JSON response) would let standard HTTP clients talk to `nsigner` without any custom framing code. The existing auth envelope (`kind:27235`) and JSON-RPC contract are unchanged; only the outer framing differs. CORS allow on the response would let browser extensions and (with TLS) HTTPS pages reach a remote `nsigner` over FIPS or any other carrier. See the discussion in [`documents/FIPS_DEPLOYMENT.md`](documents/FIPS_DEPLOYMENT.md) section 9 ("Next hardening steps").
|
||||
- **HTTPS / WSS (TLS) listener modes.** `--listen https:HOST:PORT` and `--listen wss:HOST:PORT` with `--tls-cert` / `--tls-key`. See [`plans/http_wss_listener.md`](plans/http_wss_listener.md).
|
||||
- **WebSocket listener mode (`--listen ws:HOST:PORT`).** For browser access with JSON text frames. See [`plans/http_wss_listener.md`](plans/http_wss_listener.md).
|
||||
- **NIP-46 bunker / relay transport.** Tracked in [`plans/nip46_bunker_mode.md`](plans/nip46_bunker_mode.md).
|
||||
- **Browser extension.** Tracked in [`plans/nsigner_browser_extension.md`](plans/nsigner_browser_extension.md). NIP-07 surface forwarding to a running `nsigner` instance.
|
||||
|
||||
@@ -717,4 +805,12 @@ These items are designed and worth doing, but are not in the current implementat
|
||||
- [`plans/feather_tinyusb_composite.md`](plans/feather_tinyusb_composite.md): firmware Phase 7b plan and outcome (TinyUSB composite USB transport)
|
||||
- [`plans/nsigner_browser_extension.md`](plans/nsigner_browser_extension.md): browser extension exposing NIP-07 over `nsigner`
|
||||
- [`plans/nip46_bunker_mode.md`](plans/nip46_bunker_mode.md): deferred NIP-46 relay-mode signer transport
|
||||
- [`plans/otp_nostr_integration.md`](plans/otp_nostr_integration.md): OTP one-time pad integration plan and design
|
||||
- [`plans/http_wss_listener.md`](plans/http_wss_listener.md): HTTP/WS/WSS listener modes plan
|
||||
- [`libotppad/`](libotppad/libotppad.h): shared OTP pad format library (XOR, ASCII armor, binary .otp, Padmé padding, state files, checksum)
|
||||
- [`src/otp_pad.c`](src/otp_pad.c) / [`src/otp_pad.h`](src/otp_pad.h): OTP pad state management for n_signer
|
||||
- [`src/http_listener.c`](src/http_listener.c) / [`src/http_listener.h`](src/http_listener.h): minimal HTTP/1.1 parser for HTTP listener mode
|
||||
- [`tools/make_test_pad.c`](tools/make_test_pad.c): generate a test OTP pad on a USB drive
|
||||
- [`tools/otp_roundtrip_test.py`](tools/otp_roundtrip_test.py): end-to-end OTP encrypt/decrypt round-trip test
|
||||
- [`examples/otp_nostr_30078.py`](examples/otp_nostr_30078.py): example — encrypt with OTP, wrap in Nostr kind 30078 event, sign
|
||||
- [`firmware/README.md`](firmware/README.md): firmware-side notes for MCU transport/UI integration
|
||||
|
||||
Reference in New Issue
Block a user