Signed manual pairing
User flow
- A opens
/fips invite, enters a requiredwss://signaling relay and optionally B's master public key inrecipient(64-character hex). - A reviews the summary and clicks Sign invitation with the connected master signer. The backend verifies the signature against the stored template.
- The plugin persists the relay, starts/restarts its incoming subscription, and
requires advert publication acceptance on that specific relay before returning
a
fips2:code. Subscription health is not yet verified. - A sends the code through an external messenger.
- B opens
/fips add, pastes it, reviews A's verified master and FIPS keys, and clicks Accept & sign reply. A targeted invite must name B's master. - After signature verification and relay publication, B's roster gains A and B receives a signed acceptance code using the same relay.
- B sends that code to A. A imports it with
/fips add, reviews B's verified identity, and clicks Complete pairing. A's roster then gains B.
The signing preview uses separate block text rows. Successful signature callbacks return a rich result with the full code and a Copy code button; the generic signing client suppresses plain-text callback messages. Pairing command errors also return rich results so relay rejection or signature validation errors remain visible after signing. “Pairing signed” alone only confirms the client signer step, not backend completion or relay acceptance.
The UI uses existing command forms and the generic nostr.signEvent client
action with the connected signer. No master secret is given to the plugin.
Text-only execution can prepare an operation, but signing needs the signer-enabled
Web UI or an externally signed template in the signedEvent callback argument.
This is not unattended signing support.
Format and verification
fips2: contains base64url JSON of a complete signed Nostr event. Kind 21195
is a local pairing-envelope convention, not a standardized FIPS event.
These envelopes are exchanged as codes, not published. Tags are empty; content
contains protocol appweaver-fips-pairing, version 2, type invite or
acceptance, a random UUID invitation ID, signing master key, authorized
compressed FIPS key, normalized relay URL, optional recipient master key, and
expiry. Acceptances reference the original signed invitation's event ID and are
addressed to A. Invitations last 24 hours; acceptance inherits the same expiry.
Verification checks signatures, kind, strict payload schema, signer/key agreement, timestamps, size limit (32 KiB code), and stored operation. A also checks the original event ID, relay, expiry, and intended recipient. Signed callbacks must match the stored template and current local FIPS identity exactly.
Signatures establish key control, not a real-world name: recognize or independently check the master key. Codes are not encrypted. Recipient binding limits who can complete them, not who can read them. A master signature authorizes the endpoint association; the FIPS handshake must still prove endpoint-key control.
Persistence and duplicate behavior
fips_pairing stores templates, original incoming invitations, local signed
events, and pending/issued/completed state. fips_pairing_receipts preserves the
remote acceptance at A. Roster insertion and completion are transactional.
fips_signal_relays stores the explicit runtime relay set.
A completed invitation cannot be accepted again. Reopening a pending incoming invite reuses its acceptance template. Outgoing invitations complete only once, including untargeted ones. Keep returned codes until both parties finish: code-recovery and cancellation UI are not implemented yet.
The public add command rejects unsigned fips1: and raw-key imports. Existing
roster entries are retained, not retroactively verified. These new tables do not
rebuild the peer table; legacy unique-master constraints still need migration.
Boundaries and remaining work
Pairing grants networking permission only, not app/project permission or shell/file access. Transport hardening remains in the implementation plan.
The runtime now uses persisted invitation relays instead of BOT_RELAYS or
kind 10050 discovery defaults. It subscribes/publishes across that explicit
set. Remote 37195 lookup remains pending: resolution uses the roster key.
Adding a relay restarts the runtime and interrupts existing FIPS sessions.
Advert acceptance proves relay receipt, not peer availability or UDP reachability. Traversal offers/answers now require relay acknowledgement and emit correlated connection diagnostics. Relay acceptance still does not prove that the recipient received or accepted the signal.