Add NIP-46 remote signer to the board login gate

The board's connect gate now offers "Remote signer (NIP-46)" alongside
NIP-07 / nsec / create-key. Pasting a bunker:// link establishes the
viewer's pubkey (for the web-of-trust filter and display) and keeps the
connection alive so getBoardSigner can sign reports and deletions
remotely this session — its whole point being that the secret never
leaves the signer app. After a reload the board reconnects read-only from
the remembered pubkey (session-only, like the editor).

The minimal NIP-46 client (parse bunker URI, NIP-44 codec with NIP-04
decrypt fallback, raw-WebSocket request/response) is ported into board.js,
which is an independent IIFE. Reuses the editor's translations for the 3
new BOARD_UI strings across all 14 languages (i18n parity passes).

Test: connect the board via a fake bunker signer doing real NIP-44
crypto, confirm it reveals as that identity, and that a report is signed
through the remote signer (kind-1984 with the signer's pubkey). All 6
engines. Full suite 360 passing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-11 15:04:12 -04:00
1 parent 29ed45533d
commit b38dea1294
5 files changed
+326 -29

No files matched your search

+13 -9
View File
@@ -276,15 +276,19 @@ de-duplicated per author+`d` identifier, keeping the latest.
never print (hidden in the `@media print` block alongside the other app
chrome).
- **The board is gated behind a connected Nostr identity.** On load it shows a
login gate (`#board-gate`) instead of the table, with three ways to connect:
a **NIP-07** extension (`window.nostr.getPublicKey()`), **pasting an `nsec`**
(decoded in-page to derive the pubkey, then discarded — the secret is never
stored or transmitted), or **creating a new key** (reuses this device's anon
key in `voxvera_nostr_anon_secret_hex` if present, else generates one, and
reveals the `nsec` with a save-it warning before continuing). On success it
reveals the table and surfaces the connected npub (`#board-identity`) with a
Disconnect control. The board only ever needs the *pubkey* (it reads, never
signs), so the connected pubkey is remembered in `localStorage`
login gate (`#board-gate`) instead of the table, with four ways to connect:
a **NIP-07** extension (`window.nostr.getPublicKey()`), a **NIP-46 remote
signer** (paste a `bunker://` link; same hand-rolled client as the editor —
it establishes the viewer pubkey via `get_public_key` and keeps the connection
so `getBoardSigner` can sign reports/deletes remotely this session, then falls
back to read-only after a reload), **pasting an `nsec`** (decoded in-page to
derive the pubkey, then discarded — the secret is never stored or transmitted),
or **creating a new key** (reuses this device's anon key in
`voxvera_nostr_anon_secret_hex` if present, else generates one, and reveals the
`nsec` with a save-it warning before continuing). On success it reveals the
table and surfaces the connected npub (`#board-identity`) with a Disconnect
control. The board only ever needs the *pubkey* to view (it reads to filter),
so the connected pubkey is remembered in `localStorage`
(`voxvera_connected_pubkey`, shared with the main client) and a return visit
reconnects from it directly — no prompt or re-entry, any method. This is
**not access control** — the events are public on the relays regardless — it
+10 -6
View File
@@ -198,15 +198,19 @@ board can show them flyers from people they trust.
riskier than a NIP-07 extension that keeps the key isolated; the input is a
password field, the value is cleared after use, and nothing is transmitted. The
**NIP-46 remote signer** (below) is now the safer mobile path.
- **NIP-46 remote signer (shipped).** The editor can connect a remote signer
- **NIP-46 remote signer (shipped — editor *and* board).** Both the editor's
publishing identity and the board's login gate can connect a remote signer
(bunker) by pasting a `bunker://` link: the user's secret stays in their signer
app (nsec.app, Amber, …) and the browser only holds an ephemeral local key. The
client is hand-rolled over a raw WebSocket using the vendored NIP-44 (with a
NIP-04 decrypt fallback) — the bundle ships no nip46 module. Connections are
**session-only** (the bunker token is never written to disk, so a reload returns
to anonymous). Covered by the cross-engine e2e suite (full connect →
get_public_key → sign_event → publish round-trip against a fake signer doing
real NIP-44 crypto).
NIP-04 decrypt fallback) — the bundle ships no nip46 module. On the board it
establishes the viewer pubkey and keeps the connection so reports/deletes are
signed remotely (read-only after a reload). Connections are **session-only**
(the bunker token is never written to disk, so a reload returns to anonymous /
read-only). Covered by the cross-engine e2e suite (editor: connect →
get_public_key → sign_event → publish; board: connect → reveal → report
signed through the remote signer — both against a fake signer doing real
NIP-44 crypto).
- **Editor identity (shipped).** The editor signs under anon / NIP-07 / imported
nsec / remote signer. A remembered nsec is PIN-encrypted at rest (PBKDF2 600k →
AES-GCM); plaintext is never stored. **Known weakness:** a short numeric PIN is