feat: FIDO2/WebAuthn passkey master identity - #25
Merged
jjohare merged 2 commits intoAug 13, 2026
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an optional FIDO2/WebAuthn passkey path to the Podkey identity, in two modes:
podkey/nostr-secret/v1+ counter byte, client-stored 32-byte derivation salt, rejection loop for invalid scalars). The passkey is the root of trust.podkey/wrap/v1); the passphrase vault remains a valid recovery path.Security & robustness
chrome.windows.createwindow (?flow=create|enable|unlock), not the toolbar action popup — Chrome destroys the action popup on blur when the authenticator UI takes focus.get()assertion, never the creation-time PRF (which some authenticators evaluate differently), so setup and every future unlock derive identically.nsecbackup and writes config before the session key, so an interrupted setup is recoverable rather than orphaned; orphaned public keys are swept on status.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 thedid:nostridentity / 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