Skip to main content

Module signer

Module signer 

Source
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 = the bunker://<remote_pubkey>?relay=...&secret=... string, encrypted-at-rest if the account uses pin/pass encryption (same path as pkey).
  • 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§

VectorAuthUrlHandler
Auth-URL handler that forwards bunker prompts to the frontend via the EventEmitter trait. The frontend receives a bunker_auth_url event and is responsible for opening the URL (in-app webview, system browser, …).
WatchedBunkerSigner
NostrSigner wrapper that emits BunkerConnectionState transitions on every signing outcome. The inner NostrConnect is cheaply clonable (internally Arc’d), so this is also Clone.

Enums§

BunkerConnectionState
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 EventEmitter so the UI can show a banner.
SignerKind
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 NostrConnect handle. None for 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 NostrConnect from a bunker:// URL + client keypair. Doesn’t connect yet — NostrConnect bootstraps lazily on the first signing call. Use prewarm() if you want the connection up before the user’s first send.
build_nostrconnect_session
Build a NostrConnect for a client-initiated session — generates the nostrconnect:// URI from the client keys + relays + Vector metadata, constructs the underlying NostrConnect with 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 None for 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
true iff 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().await the old one to drain its relay pool cleanly.
set_bunker_state
Install a new bunker state and fan out a bunker_state event 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.