mirror of
https://github.com/PR0M3TH3AN/bitlogin.git
synced 2026-10-27 08:31:21 +00:00
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]>
135 lines
7.7 KiB
Plaintext
135 lines
7.7 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.
|
|
- 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'`) and `detail.capabilities`
|
|
(`{ nip44, nip04, getRelays }`) tell your app which backend is active and
|
|
what it supports.
|
|
- 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() }`. Use
|
|
`window.bitlogin.isActiveSigner()` to tell whether BitLogin specifically
|
|
(not another extension) currently backs `window.nostr`, and
|
|
`releaseSigner()` to hand `window.nostr` back (a safe no-op if BitLogin
|
|
doesn't hold it) when a user switches to a different signing method 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.
|
|
- `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: `bitlogin-login` (`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`
|
|
(`detail.origin`).
|
|
- 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.
|