Owner decision: static only, and no per-site provider registration --
which every OAuth path requires (and GitHub/Facebook additionally require
a server). Both on-ramp implementations (service handshake, serverless
Google Drive) are removed the day they shipped; centralized-onramps.md
stays on the record as the analysis of why, superseded by
passkey-login.md.
The passkey rail: a WebAuthn PRF-capable passkey (synced for most users
by Google Password Manager or iCloud Keychain) deterministically derives
the login name and password of a standard capsule account -- passkey.ts
holds the derivation behind a FROZEN v1 known-answer contract with the
same standing rule as knownAnswer.test.ts. The ordinary password flows
do everything else; nothing is stored outside the user's authenticator,
and the rail needs no network of its own.
Flow is idempotent by construction: sign-in against a passkey with no
account routes into setup with the derived credential
(AccountNotFoundError); setup against an existing account signs in
(AccountAlreadyExistsError). Fresh identity or §SF10 nsec import at
setup; Tier B2 phrase card retained ('your account stays yours even if
you lose your passkey'); rotation is the graduation step and says so.
Pins updated: passkey row with honest custody label inside the menu,
OAuth actions provably absent, element still network-free. 11 passkey
tests incl. the frozen vectors; menu and screens verified in Chrome.
Co-Authored-By: Claude Fable 5 <[email protected]>
Proposes 'Sign in with Google/GitHub/…' as a clearly labeled fourth
method family behind the same login surface. Two architectures: an
OAuth-gated NIP-46 bunker (works today, full custody, the proof point)
and the recommended one -- OAuth as the password manager for a REAL
capsule account, where the service holds only the unlock secret, the
identity lives on relays, and service compromise maps to the password-
compromise analysis the spec already has (§14).
Phrase handling is a labeled custody dial (shown at signup / first-
session-only with a 'secure your account' card / escrowed). Graduation
keeps the same npub at every step: save the npub, claim the phrase,
hold your own password, full export -- with §SF10 import as the
already-implemented escape from any custodial tier. Honesty contract:
every centralized option answers who can access the account, what
happens if they vanish, and how to leave. Conforming services must
export and delete on demand and publish their tier machine-readably.
Owner framing on the record: meet users at whatever sign-in they trust,
nudge toward decentralization over time -- 'it's a journey for all of us.'
Co-Authored-By: Claude Fable 5 <[email protected]>
Implements login-methods.md §LM5 phase one. Core gains nostr/nip46.ts -- a
Nip46Client speaking kind-24133 RPC with NIP-44 transport encryption,
bunker:// parsing, nostrconnect listen (secret echo is proof-of-scan),
auth_url surfacing, and typed deadline errors -- plus subscribeLive on
RelayConnection with re-REQ on reconnect, and live post-EOSE delivery in
the MockRelay so tests exercise real round trips against a fake bunker.
The ephemeral client key and Nip46Client live in the crypto worker
(§LM5.4); nip46* actions bypass the serial queue so a signer waiting
minutes for human approval cannot block other worker calls, and the
signer's auth_url reaches the UI as the worker's first notification frame.
The widget's connect screen runs both legs at once: a nostrconnect QR --
rendered as dot-style inline SVG with rounded finder eyes, per owner
preference -- for phone signers, and a bunker:// paste box (password
input; the URI carries a token). window.nostr is now an element-routed
provider, so a NIP-46 session claims the slot and hosts reading it get
the remote signer transparently; NIP-07 still leaves the slot to the
extension. Sessions stay thin per §LM7: no vault, no persistence.
Persistence remains gated on the external vault review (§LM5.3).
14 new tests (core fake-bunker e2e, QR geometry, signer delegation) and
two policy pins: bunker option below the password path, element stays
web-storage-free.
Co-Authored-By: Claude Fable 5 <[email protected]>
Implements login-methods.md §LM4. New signers/ layer in the widget:
detectForeignNip07Provider never mistakes BitLogin's own provider for an
extension, and Nip07Signer wraps the snapshotted provider with capability
probing (typed SignerUnsupportedError instead of TypeErrors off missing
nip44), deadlines on every call (SignerTimeoutError -- no silent hangs),
pubkey validation, and unsigned-event normalization.
The element routes its public API through the active signer, so hosts get
the extension backend transparently. Extension sessions are thin (§LM7):
no capsule, no vault (requestNwcConnection resolves null), no persistence,
and window.nostr is left to the extension -- claimSigner stays reserved
for BitLogin-account sessions. bitlogin-login now carries method and
capabilities; publicKey is unchanged for existing hosts.
Password-first pinned (owner decision, §LM9.1): the extension option
renders as a secondary link under the primary sign-in, enforced in
securityPolicy.test.ts alongside a pin that the extension confirm path
never claims window.nostr or writes storage. 14 new signer tests.
Co-Authored-By: Claude Fable 5 <[email protected]>
Proposes aggregating sign-in methods behind the existing provider facade
instead of being one option in a host's login menu. NIP-07 delegates to an
installed extension; NIP-46 connects to a remote signer, memory-only until
the vault review gate clears. Rejects nsec paste as a login method on the
record — §SF10 import is the sanctioned path for raw keys. Non-BitLogin
sessions are signer-only: no capsule, no vault, no portable state.
Co-Authored-By: Claude Fable 5 <[email protected]>