Files
bitlogin/packages/demo/public/llms.txt
T
Cursor AgentandthePR0M3TH3AN f5b2f78da1 Add pubkey identity alias and colon-style DOM event aliases
Host identity surfaces now always expose pubkey (lowercase hex), with
publicKey kept as a deprecated alias of the same value. Every existing
hyphenated widget event also fires a bubbling, composed bitlogin:<verb>
equivalent immediately after, without changing old dispatch so a listener
on the hyphen name cannot fire twice.

Co-authored-by: thePR0M3TH3AN <[email protected]>
2026-10-08 19:14:25 +00:00

161 lines
9.6 KiB
Plaintext

# BitLogin
> A static, relay-backed portable Nostr account protocol. A familiar login
> name and password (or an existing `nsec`) unlocks a portable Nostr identity
> stored as encrypted "capsules" on public Nostr relays — no account server,
> no password-reset email, no database. Ships as an embeddable custom element
> plus a NIP-07-compatible `window.nostr` provider that any static site can
> drop in with one script tag.
## Integrate in one snippet
```html
<script type="module" src="/vendor/bitlogin/bitlogin.js"></script>
<bitlogin-auth
vault-relays="wss://relay-one.example,wss://relay-two.example,wss://relay-three.example"
discovery-relays="wss://discovery-one.example,wss://discovery-two.example">
</bitlogin-auth>
```
`bitlogin.js` and `cryptoWorker.js` (plus the shared chunk they both import)
must be deployed side by side — the main script resolves the worker's URL at
runtime relative to its own location, so this works from any path. For
production, self-host these files rather than depending on the demo
deployment above.
## What it gives your app
- A `<bitlogin-auth>` custom element (Shadow DOM, no CSS collisions) handling
create account, sign in, forgot-password recovery via a 12-word phrase, and
password rotation.
- "Continue with a passkey": zero site setup, zero servers, zero
registration with any provider. A WebAuthn PRF-capable passkey (synced
for most users by Google Password Manager or iCloud Keychain)
deterministically derives the login name + password of a standard capsule
account (frozen v1 contract in the widget's passkey.ts); the ordinary
password flows do the rest, and nothing is stored outside the user's own
authenticator. Synced passkeys survive device loss; losing the PLATFORM
account (or the site changing domain, which changes the WebAuthn RP id)
is recovered by the 12-word phrase, whose ceremony is MANDATORY at
signup -- no session is granted until it is completed, because a passkey
account has no custodian who could help later. One passkey = one account (PRF is
per-credential); passkeys are per-site (rpId) -- cross-site portability
is the capsule account's job. Rotating the password is the graduation
step to fully self-held. Requires a PRF-capable browser/authenticator;
the widget shows an honest fallback message otherwise.
- One login surface, several methods: if the visitor's browser has a NIP-07
signer extension, the welcome screen offers "Use your Nostr extension
instead" below the primary username/password path, and every visitor gets
"Use a remote signer (bunker)" -- a NIP-46 connect screen with a scannable
nostrconnect QR for phone signers (Amber, nsec.app, ...) and a bunker://
paste box. The element routes `getPublicKey`/`signEvent`/`nip44*`/`nip04*`
through whichever signer backs the session. Alternative-method sessions are
signer-only: no vault (`requestNwcConnection` resolves null), no
persistence (a reload ends the session). NIP-07 sessions leave
`window.nostr` to the extension; NIP-46 sessions claim it, backed by the
worker-held remote-signer client. `bitlogin:login`'s `detail.method`
(`'bitlogin' | 'nip07' | 'nip46'`), `detail.pubkey` (lowercase hex;
`detail.publicKey` is a deprecated alias), and `detail.capabilities`
(`{ nip44, nip04, getRelays }`) tell your app which backend is active and
what it supports. The hyphenated `bitlogin-login` still fires.
- A `window.nostr` provider shaped like the de facto NIP-07 interface
(`getPublicKey`, `signEvent`, `getRelays`, `nip44.encrypt`/`decrypt`) —
existing Nostr app code that already calls `window.nostr` works unmodified,
same as it would against a browser extension (Alby, nos2x, …).
- `window.bitlogin` — installed as soon as the script loads, independent of
login state: `{ version, isActiveSigner(), releaseSigner(), activeMethod(),
activeSession() }`. Two distinct questions: `activeMethod()` answers "is
someone signed in through BitLogin, and how" ('bitlogin' | 'nip07' |
'nip46' | null; `activeSession()` adds pubkey, deprecated publicKey, and
npub — public data only), while `isActiveSigner()` answers "does BitLogin
currently back window.nostr". They deliberately diverge during NIP-07
sessions: the extension rightly owns window.nostr (isActiveSigner() false)
while the user IS signed in through BitLogin (activeMethod() 'nip07'). Use
activeMethod()/the bitlogin:login event for session state, never
isActiveSigner(). `releaseSigner()` hands `window.nostr` back (safe no-op
if BitLogin doesn't hold it) when a user switches signing methods in your
own UI.
- The `<bitlogin-auth>` element also exposes instance-scoped
`claimSigner()` / `releaseSigner()` methods for the same purpose;
`claimSigner()` is called automatically on every successful sign-in,
sign-up, or recovery, and `releaseSigner()` on logout and on removal
from the DOM — so by default a page with a single widget never needs to
call either manually. Without this, `window.nostr` would stay pointed at
a signed-out BitLogin provider forever, blocking a switch to another
method without a page reload.
- `requestNwcConnection({ appName, reason })` on the element — asks the user
to share an NWC wallet connection with the page's origin and resolves the
full `nostr+walletconnect://` URI (or null if declined). Handles sign-in,
one-tap approval for an origin-bound stored connection (connections are
encrypted Connection Vault records that follow the user across devices),
or first-time import via Bitcoin Connect's chooser / a pasted URI. REVEAL
mode: the origin receives the full bearer credential — wallet-side budgets
are the real security boundary. Requires `style-src-attr 'unsafe-inline'`
in the host CSP. Fires `bitlogin:connection-granted` after each share
(hyphenated `bitlogin-connection-granted` still fires, deprecated).
- `offerNwcConnection(uri, { appName, label })` on the element — the inverse:
the app obtained an NWC URI by its own means and offers the user a portable
copy. Consent screen in the widget's own UI, never a silent save; resolves
'saved' | 'declined' | 'already-saved' (same wallet+secret exists; origin
binding refreshed, no UI) | 'unavailable' (no session/vault — treat as
no-op). Safe to fire after every successful connect.
- Events on the element: prefer colon names (`bitlogin:login` with
`detail.pubkey` / deprecated `detail.publicKey`, `detail.method`,
`detail.capabilities`; `bitlogin:logout`; `bitlogin:signer-claimed`;
`bitlogin:signer-released` — the last also fires on `window` when
triggered via `window.bitlogin.releaseSigner()`;
`bitlogin:connection-granted` with `detail.origin`; plus `bitlogin:ready`
and `bitlogin:error`). Hyphenated names (`bitlogin-login`, …) still fire
unchanged and are deprecated. Colon events bubble/composed; hyphen events
do not.
- Private keys live in a dedicated Web Worker and the signing APIs return only
public keys, signed events, and ciphertext. LIMIT: an embedded widget is
same-origin with its host page, so a malicious or XSS-compromised host can
reach the worker directly (including exportIdentity) and can overlay the
consent UI. Treat the host page as trusted; embed only in sites you control.
- Removing a wallet connection from BitLogin deletes the stored copy only. It
is NOT revocation -- spending authority is revoked at the wallet.
- Losing BOTH the password and the recovery phrase is permanent: there is no
custodial recovery, by design. An account also depends on relays retaining
its encrypted capsule; download the recovery export as an offline fallback.
## Key facts for integration
- Build or obtain a reviewed release, then self-host its complete module graph
on the application's own origin and load it with `<script type="module">`.
- Don't give the element `id="bitlogin"` — browsers auto-expose elements with
a matching `id`/`name` as a same-named global, which shadows
`window.bitlogin`. Use `id="bitlogin-widget"` or similar.
- Configure relays via the `vault-relays` and `discovery-relays` attributes
(comma-separated `wss://` URLs); omit to use BitLogin's small built-in
bootstrap list.
- Human-chosen passwords are disabled. The widget always generates a
high-entropy passphrase because a downloadable encrypted capsule can be
guessed against offline forever.
- Recovery is a 12-word BIP-39-style phrase, independent of the password;
losing the password never locks the user out if they still have the
phrase.
## Reference docs
- [Integration guide](/docs.html) — full walkthrough with code samples.
- [Live account manager demo](/account.html) — create/login/recover, sign a
test event, rotate password, export identity.
- [Protocol specification](https://github.com/PR0M3TH3AN/bitlogin/blob/main/docs/spec.md) —
the full BitLogin protocol (crypto construction, capsule formats,
relay architecture, threat model).
- [Second-factor / recovery options design notes](https://github.com/PR0M3TH3AN/bitlogin/blob/main/docs/second-factor.md)
and [future recovery-path options](https://github.com/PR0M3TH3AN/bitlogin/blob/main/docs/future-recovery-options.md) —
proposals, not all implemented; each notes its own status.
- [Source repository](https://github.com/PR0M3TH3AN/bitlogin) — monorepo:
`packages/core` (protocol implementation), `packages/widget` (the embeddable
element + provider), `packages/demo` (this site).
## Status
Experimental alpha, NOT independently audited. Verified with an in-memory
mock-relay test suite, real-browser end-to-end runs, and an internal
adversarial audit (2026-07-31) whose findings are recorded in
docs/connection-vault.md §19 and the CHANGELOG. See the README
in the source repository for the full implemented/deferred feature list.