Skip to content

feat: FIDO2/WebAuthn passkey master identity - #25

Merged
jjohare merged 2 commits into
JavaScriptSolidServer:mainfrom
jjohare:feat/fido2-passkey-identity
Aug 13, 2026
Merged

feat: FIDO2/WebAuthn passkey master identity#25
jjohare merged 2 commits into
JavaScriptSolidServer:mainfrom
jjohare:feat/fido2-passkey-identity

Conversation

@jjohare

@jjohare jjohare commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds an optional FIDO2/WebAuthn passkey path to the Podkey identity, in two modes:

  • derived — the Nostr secp256k1 key is derived from a WebAuthn PRF output via HKDF-SHA256 (info podkey/nostr-secret/v1 + counter byte, client-stored 32-byte derivation salt, rejection loop for invalid scalars). The passkey is the root of trust.
  • wrapped — an existing key is AES-256-GCM wrapped under HKDF-SHA256 of the PRF output (info podkey/wrap/v1); the passphrase vault remains a valid recovery path.

Security & robustness

  • Ceremony window: all WebAuthn ceremonies run in a dedicated chrome.windows.create window (?flow=create|enable|unlock), not the toolbar action popup — Chrome destroys the action popup on blur when the authenticator UI takes focus.
  • Assertion-time PRF: key material always comes from a get() assertion, never the creation-time PRF (which some authenticators evaluate differently), so setup and every future unlock derive identically.
  • Backup gate + safe ordering: derived creation is gated on an acknowledged nsec backup and writes config before the session key, so an interrupted setup is recoverable rather than orphaned; orphaned public keys are swept on status.
  • Sender guard: privileged background messages are rejected unless the sender is one of our own extension pages, keyed on the extension origin — not sender.tab, since the ceremony window is itself a tab (this distinction was a real bug caught in browser testing).

Docs

Two draft specs under site/, modelled on the did-nostr method spec: the passkey key-custody contract (with reproducible test vectors) and the did:nostr identity / NIP-07 / NIP-98 surface.

Verification

Driven end-to-end in real Chrome via a CDP virtual authenticator (hasPrf): create → backup gate → derived identity → lock → passkey unlock reproduces the same pubkey. Also 169 unit tests pass; lint and build clean.

🤖 Generated by Claude Code

Add optional passkey-based identity to the MV3 Nostr signer, in two modes:

- derived: the Nostr secp256k1 key is derived from a WebAuthn PRF output via
  HKDF-SHA256 (info "podkey/nostr-secret/v1" + counter byte, client-stored
  32-byte derivation salt, rejection loop for invalid scalars).
- wrapped: an existing key is AES-256-GCM wrapped under HKDF-SHA256 of the PRF
  output (info "podkey/wrap/v1"), keeping passphrase recovery intact.

Security and robustness:

- All WebAuthn ceremonies run in a dedicated chrome.windows.create window
  (?flow=create|enable|unlock), not the toolbar action popup, which Chrome
  destroys on blur when the authenticator UI takes focus.
- Key material is always obtained from a get() assertion (never the creation
  PRF), so setup and every future unlock derive identically.
- Derived-identity creation is gated on an acknowledged nsec backup, and writes
  config before the session key so an interrupted setup is recoverable, not
  orphaned; orphan public keys are swept on status.
- Privileged background messages (SET_SESSION_KEY, GENERATE/IMPORT_KEYPAIR,
  UN/LOCK_VAULT, GET_KEYPAIR_STATUS) are rejected unless the sender is one of
  our own extension pages, keyed on the extension origin (not sender.tab, since
  the ceremony window is itself a tab).

Docs: two draft specs under site/ modelled on the did-nostr method spec — the
passkey key-custody contract (with test vectors) and the did:nostr identity /
NIP-07 / NIP-98 surface.

Verified end-to-end in Chrome via a CDP virtual authenticator: create → backup
gate → derived identity → lock → passkey unlock reproduces the same pubkey.
169 unit tests pass; lint and build clean.

Co-Authored-By: jjohare <github@thedreamlab.uk>
…egulated use

Name the audience on the advanced-identity disclosure so the higher-friction
FIDO2 path reads as a deliberate best-practice choice (agent management,
compliance) rather than an unexplained option — consistent with the framing
used on the forum and the dreamlab-ai site.

Co-Authored-By: jjohare <github@thedreamlab.uk>
@jjohare
jjohare merged commit ed56225 into JavaScriptSolidServer:main Aug 13, 2026
2 checks passed
@jjohare
jjohare deleted the feat/fido2-passkey-identity branch August 13, 2026 12:37
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.

1 participant