Commit Graph
4 Commits
Author SHA1 Message Date
thePR0M3TH3AN 01fc7c5ed3 Refuse to overwrite an existing account on register or password change
registerAccount() never checked whether a credential capsule already
existed at the locator address it was about to publish to. That
address is a NIP-33 replaceable event fully determined by login name +
password, so registering with a login name + password combination that
was already registered -- by mistake (double-clicking "create account",
confusing it with "sign in"), or by coincidence with someone else's
account under a shared login name -- silently replaced the existing
account's identity binding. The old everyday identity became
unreachable through that login unless the original recovery phrase was
still on hand, with nothing warning the user beforehand.

changePassword() had the identical gap on the new locator: rotating to
a password that happened to already be registered under the same login
name would silently destroy that other account.

Both flows now query for an existing capsule at the target locator
before publishing, and refuse with AccountAlreadyExistsError if one is
found (or a retry-oriented error if no relay could be reached to check).
Two new end-to-end tests reproduce both scenarios and confirm the
pre-existing account survives untouched.

Bumps docs/spec.md to v0.6 (§15.6, §18.1).
2026-07-21 16:54:32 -04:00
thePR0M3TH3AN 8c01a121df Never overwrite an existing profile or relay list on import
After every successful registration -- including nsec imports -- the
widget unconditionally published a default kind 0 profile ({name:
loginName}) plus kind 10002/10050 relay lists. All three are
NIP-01/NIP-65/NIP-17 replaceable events, so importing an nsec that
already had a real profile (display name, bio, avatar, NIP-05) and/or
relay preferences published elsewhere silently erased all of it,
replacing it with just the chosen BitLogin login name and the vault
relays.

publishInitialProfile() (moved into @bitlogin/core so it can be
covered by the existing MockRelay-based test harness) now queries for
an existing event of each kind before publishing, and only fills in
whichever one is genuinely missing. A fresh registration has nothing
to find, so behavior there is unchanged; an import now leaves
whatever the identity already had untouched.

Bumps docs/spec.md to v0.5 (§15.8, §28.1) and adds two new core tests
covering the fresh-identity and existing-profile-preserved cases.
2026-07-21 16:39:00 -04:00
thePR0M3TH3ANandClaude Sonnet 5 1dc5e6802c Fail closed on credential-generation rollback; fix NIP-44 extended-length payloads
Found during a real integration pass (embedding this widget in BitRoad, a
separate Nostr commerce app):

- Password rotation's old-locator tombstone (§18.1) is only advisory -- a
  relay that never processes it can keep serving the pre-rotation capsule
  indefinitely. loginWithPassword/changePassword previously accepted that
  stale capsule with only a non-blocking warning. A device that has already
  seen the newer generation now refuses it (RollbackDetectedError) unless the
  caller explicitly passes acknowledgeRollback: true for the narrower
  relay-lag case. The widget surfaces this as a blocking confirmation screen
  before claiming the signer, not a banner shown after the fact.
- NIP-44 capped plaintext at 65535 bytes; added the extended-length prefix
  for payloads up to 2^32-1 bytes, verified byte-for-byte against the
  nostr-tools reference implementation (conversation keys, padding at every
  bucket boundary, round trips in both directions).
- <bitlogin-auth> now exposes nip44Encrypt/nip44Decrypt directly on the
  element instance, alongside the existing getPublicKey/signEvent, so a host
  page can reach every capability without racing other providers for the
  single window.nostr slot.

docs/spec.md bumped to v0.4 with the full writeup (§16.2, §29.7). 75/75
tests passing (14 new NIP-44 tests, 2 new rollback e2e tests).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-07-21 14:36:35 -04:00
Claude 5ee63ae6b0 Implement BitLogin as a modular protocol library, embeddable widget, and demo site
Builds out the v0.3 spec as an npm workspace monorepo any static site can
consume for login and Nostr signing:

- packages/core: framework-free protocol implementation — Argon2id/HKDF/
  ScalarExpand/AES-256-GCM/JCS crypto primitives, NIP-01 event handling and a
  quorum-aware relay pool, credential/recovery capsule construction and
  validation, and the full account lifecycle (registration, any-device
  login with rollback/relay-disagreement detection, phrase recovery with
  recovery-capsule refresh, password rotation with mandatory tombstone +
  NIP-09 deletion, replica repair, recovery export, NIP-19 encoding, a
  NIP-44 v2 implementation, and a NIP-07-shaped signer). 49 tests cover the
  spec's Phase 0 byte-level vectors plus full create/login/recover/rotate
  end-to-end scenarios against an in-memory mock relay.

- packages/widget: a <bitlogin-auth> custom element with a dedicated crypto
  Web Worker (private keys never touch the main thread) and a window.nostr
  NIP-07-compatible provider, so existing Nostr web apps work against it
  with zero code changes. Built with Vite into two relocatable static files.

- packages/demo: a plain static site (no build step of its own) consuming
  the widget — a branding/landing page, an integration guide, and an
  account-manager page covering create/login/recover, password rotation,
  and identity export.

All flows were verified twice: protocol-level tests against a mock relay,
and the actual built widget driven end-to-end in real Chromium (registration,
phrase confirmation, event signing, clean-device login, window.nostr, and
phrase recovery all reproduce the identical everyday pubkey).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GWJbXXhvTx9wo59jSz57J6
2026-07-21 03:44:10 +00:00