Skip to content

Multi-signer support: NIP-46 (Amber/bunker), NIP-55 Android intents, nsec paste - #7

Merged
sovITxyz merged 4 commits into
mainfrom
feat/signer-expansion
Jul 17, 2026
Merged

Multi-signer support: NIP-46 (Amber/bunker), NIP-55 Android intents, nsec paste#7
sovITxyz merged 4 commits into
mainfrom
feat/signer-expansion

Conversation

@sovITxyz

Copy link
Copy Markdown
Owner

Closes #2.

What

Signing was NIP-07-only. This PR introduces a client-side signer abstraction with four backends — the existing browser extension plus three new ways to sign in and publish:

  • NIP-46 remote signer (bunker:// paste or client-initiated nostrconnect://) — Amber, nsec.app; kind-24133 RPC over relay wss, NIP-44 encryption with NIP-04 legacy auto-detect, auth_url approval links (https-only).
  • NIP-55 Android intents — deep-link to Amber with returnType=signature; the unsigned event (pubkey + precomputed id) is stashed locally, reassembled and schnorr-verified on callback; publish/delete resume across the redirect with retry-without-re-sign and discard.
  • nsec paste — in-browser signing via the vendored crypto bundle. ⚠️ Deliberate product decision: the key persists in plain localStorage; the UI carries an explicit warning and a "Forget this key" affordance. Strict CSP (script-src 'self') is the compensating control.

Plus groundwork the relay work (#5) will reuse: the editor's relay broadcast now speaks NIP-42 AUTH (signs the challenge via the active signer, resends the event after auth; skipped for redirect signers).

How

  • public/js/vendor/nostr-crypto.js — committed, esbuild-generated IIFE (npm run build:vendor) from the existing @noble/* deps: NIP-01 id/signing byte-identical to src/nostr/event.ts, NIP-44 v2 (validated against official vectors + nostr-tools interop both directions), bech32 ported from src/nostr/nip19.ts, WebCrypto RNG only. CI rebuilds and fails on drift.
  • public/js/signer-core.js / signer.js / signer-nip46.js — pure cores + dispatcher, per repo IIFE conventions.
  • Server logic untouched/login and /api/mirror contracts and CSP unchanged (headers.spec.ts passes unmodified). Views/copy updated; no new runtime deps (esbuild + @noble/ciphers are pinned devDeps).

Security notes

  • Adversarially reviewed in three passes (crypto correctness incl. NIP-44 vectors and RNG; secret-handling/storage; redirect/protocol flows); all confirmed findings fixed, including: random NIP-46 request ids (response-replay), envelope schnorr verification before decrypt, response-matches-request assertion, NIP-55 callbacks bound to their pending-flow type behind an explicit marker, auth_url restricted to https.
  • Secret keys never appear in any network request, URL, or intent payload (audited; the only POST bodies are signed events).

Testing

  • 699/699 tests across 39 files (139 new: crypto vectors/parity, signer core, NIP-46 state machine with scripted transport).
  • docs/manual-signer-tests.md — real-device checklist (Amber bunker + NIP-55, nsec.app incl. auth_url, nsec paste, NIP-07 regression, devtools no-secret audit). Device pass required before merge.

sovITxyz added 4 commits July 16, 2026 16:41
…port

esbuild-bundled from the existing @noble deps into a committed plain-IIFE
artifact (public/js/vendor/nostr-crypto.js): NIP-01 event id/signing
byte-identical to src/nostr/event.ts, NIP-44 v2 + NIP-04, bech32
(npub/nsec) ported from src/nostr/nip19.ts, WebCrypto RNG only.
Cross-checked in tests against the server modules, nostr-tools, and
official NIP-44 vectors; CI rebuilds and fails on artifact drift.
…NIP-46 remote-signer client

signer-core.js: pure helpers (nsec decode, NIP-55 intent build/parse,
pending-record validation). signer.js: dispatcher with method persistence,
redirect-signer resume (schnorr-verified against recomputed event id), and
backend registry. signer-nip46.js: bunker:///nostrconnect pairing, kind-24133
RPC over relay wss with NIP-44 (NIP-04 fallback), envelope schnorr
verification, random request ids, response-matches-request assertion,
auth_url surfacing. 63 unit tests incl. scripted-transport state machine.
…-55/nsec)

Login page grows a signer picker (extension / remote signer via bunker:\/\/
or nostrconnect:\/\/ / Amber on Android / paste-key with an explicit
unencrypted-localStorage warning); all methods converge on the unchanged
kind-22242 challenge flow. Editor signs through NbreadSigner with a
pubkey-vs-session mismatch guard, NIP-55 stash/resume (mirror before
broadcast, retry-without-re-sign, discard on rejection), and a NIP-42 AUTH
client in the relay broadcast (auth-required OK handling, 8s deadline,
redirect signers skip AUTH). Signer stack hardened: NIP-55 callbacks
require the explicit marker and are bound to their pending-flow type.
docs/home/privacy copy now describes all four signing methods (and the
localStorage caveat for pasted keys); public/js README documents the
signer stack and load order; ops.md notes the vendor-bundle drift guard;
docs/manual-signer-tests.md is the Amber/nsec.app/NIP-55/nsec-paste
device checklist covering issue #2 acceptance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Amber signer support (NIP-46 / NIP-55) for mobile sign-in and publishing

1 participant