mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-27 08:31:21 +00:00
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]>
161 lines
9.6 KiB
Plaintext
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.
|