Expand description
Polymorphic signer — local key vault vs. NIP-46 remote bunker.
Vector supports two signer modes per account:
- Local — the user’s nsec lives in
MY_SECRET_KEY(GuardedKey vault) on this device. Signing is local; the key materialises in plaintext only for microseconds per operation. - Bunker — the user’s nsec lives on a remote NIP-46 signer (Amber,
nsec.app, …). Vector holds only a client keypair (in
MY_SECRET_KEY) used to RPC the bunker. Every signing request takes a round-trip; the user’s identity key never touches this device.
The discriminator is persisted in the per-account settings DB
(signer_type key) and materialised into the SIGNER_KIND atomic at
login. Hot paths read the atomic; cold paths read the DB directly.
Storage layout for bunker accounts (see db::settings):
signer_type = "bunker"bunker_url= thebunker://<remote_pubkey>?relay=...&secret=...string, encrypted-at-rest if the account uses pin/pass encryption (same path aspkey).bunker_remote_pubkey= the signer’s pubkey, plaintext (routing only).pkey= the NIP-46 client keypair (encrypted-at-rest under the same path as local accounts). Reusing the existing vault avoids a second GuardedKey slot; see the “Client-keypair storage note” section below.
Structs§
- Vector
Auth UrlHandler - Auth-URL handler that forwards bunker prompts to the frontend via the
EventEmittertrait. The frontend receives abunker_auth_urlevent and is responsible for opening the URL (in-app webview, system browser, …). - Watched
Bunker Signer NostrSignerwrapper that emitsBunkerConnectionStatetransitions on every signing outcome. The innerNostrConnectis cheaply clonable (internally Arc’d), so this is also Clone.
Enums§
- Bunker
Connection State - Observable state of the bunker connection. The atomic backs hot-path reads
(e.g. send paths checking “is it safe to issue a sign call?”); state changes
also fan out to the frontend via
EventEmitterso the UI can show a banner. - Signer
Kind - Which signer backs the active account.
Constants§
- VECTOR_
APP_ ICON - Icon shown by the signer alongside the app name. PNG, served from the public GitHub mirror so the URL stays valid even if vectorapp.io changes its asset layout. Signers cache by URL, so a stable target avoids re-fetches on every pairing.
- VECTOR_
APP_ NAME - Application name shown to the user by the remote signer when approving the connection (e.g. on Amber’s pairing screen).
- VECTOR_
APP_ URL - Marketing site — surfaced as the signer’s “More info” link.
- VECTOR_
NIP46_ PERMS - NIP-46 permission scope Vector requests on client-initiated pairings.
Statics§
- BUNKER_
SIGNER - Active
NostrConnecthandle.Nonefor local-signer sessions.
Functions§
- attempt_
bunker_ login - Build a
NostrConnect, attach the Vector auth-URL handler, bootstrap it, and install it as the active bunker signer. Returns the discovered remote signer pubkey on success. - build_
bunker_ signer - Build a
NostrConnectfrom abunker://URL + client keypair. Doesn’t connect yet —NostrConnectbootstraps lazily on the first signing call. Useprewarm()if you want the connection up before the user’s first send. - build_
nostrconnect_ session - Build a
NostrConnectfor a client-initiated session — generates thenostrconnect://URI from the client keys + relays + Vector metadata, constructs the underlyingNostrConnectwith the Vector auth-URL handler already attached, and returns both for the caller to (a) display the URI to the user (QR + copy button) and (b) install the signer. - build_
nostrconnect_ uri - Build a client-initiated
nostrconnect://URI. The user copies this URL into their signer app (or scans the QR rendering of it); the signer initiates the connection back to the listed relays. - bunker_
signer - Snapshot the active bunker handle. Returns
Nonefor local-signer sessions. - bunker_
state - Read the live bunker connection state. Backed by an atomic; cheap to call.
- drain_
bunker_ state - Clear all bunker-specific state. Called by
reset_session()so a swap from bunker → local (or between two bunker accounts) leaves no stale keying material, relay-pool handles, or stale connection-state observed by the frontend. The caller is responsible for.shutdown().await-ing the returned signer outside the lock. - is_
bunker trueiff the active account signs via a remote NIP-46 bunker. Hot-path helper for code that needs to branch on signer mode (e.g. parallelising gift-wrap signing harder when each call pays a round-trip).- parse_
bunker_ relays - Parse a
bunker://URL and return the relay URLs it lists. Used by the Settings UI to render “Connected via” without re-bootstrapping. Returns an empty Vec on any parse failure — the caller treats this as a display-only signal and renders a generic fallback instead of erroring. - parse_
bunker_ remote_ pubkey - Inspect a
bunker://URL without bootstrapping: returns the remote signer’s pubkey (hex). Used by login flows to check whether a re-submitted URL points at the same bunker as the active session (idempotent re-login) versus a different bunker (which requires logout first). Cheap — no network. - prewarm_
bunker - Force a bunker bootstrap and discover the user’s identity pubkey.
- set_
bunker_ signer - Install the bunker handle for the active session. Replaces any prior handle
without shutting it down — callers swapping should
take_bunker_signer()first and.shutdown().awaitthe old one to drain its relay pool cleanly. - set_
bunker_ state - Install a new bunker state and fan out a
bunker_stateevent to the frontend. No-op if the state didn’t change — avoids spamming the UI with duplicate transitions when a signing call confirms what’s already known. - set_
signer_ kind - Install the signer kind for the active session. Call at login after the settings row has been read, and on swap before any signing work runs.
- signer_
kind - The signer kind for the active session. Cheap to read; backed by an atomic.
- take_
bunker_ signer - Atomically remove the bunker handle. Used by session teardown so the
caller can
.shutdown()it without racing readers. - vector_
metadata - Build the NIP-46 metadata payload Vector advertises in client-initiated
nostrconnect://URIs. The signer reads this to render the approval prompt — name and icon are the bits the user actually sees.