Files
bitlogin/docs/account-switching.md

9.8 KiB

BitLogin Multi-Account Switching

Status: Experimental design Document version: 0.1 Date: July 22, 2026 Implementation status: Not implemented

1. Summary

BitLogin should support keeping multiple independently authenticated accounts on a device and switching between them without repeating the full password and relay-login flow every time.

The first implementation should treat each account as an independent BitLogin account with its own credentials, recovery phrase, capsules, Nostr identity, relay preferences, messages, application state, connections, and entitlements.

BitLogin on one device
├── Personal account
├── Business account
├── Anonymous account
└── Additional imported account

This feature is primarily a local session and user-interface capability. It does not initially change the BitLogin wire protocol or place several independent accounts beneath one recovery phrase.

2. Motivation

Nostr users commonly maintain more than one identity:

  • A personal identity
  • A business or project identity
  • An anonymous or pseudonymous identity
  • A test identity
  • An identity dedicated to a specific application or community

Today, switching identities often requires logging out, re-entering credentials, reloading extensions, or manually importing keys. BitLogin can make account switching feel like an ordinary multi-profile application while preserving the fact that each Nostr identity is independently owned and recoverable.

The product goal is:

Add an account once, then switch identities deliberately and visibly.

3. Initial scope: independent accounts

Each remembered account remains cryptographically independent:

Account A
├── login name + password
├── recovery phrase
├── credential capsule
├── recovery capsule
└── everyday Nostr identity

Account B
├── login name + password
├── recovery phrase
├── credential capsule
├── recovery capsule
└── everyday Nostr identity

Compromise or recovery of one account must not automatically expose another.

The local account switcher may remember encrypted or origin-scoped session material for several accounts, but it must not merge their remote records or recovery paths.

4. Future scope: identity collections

A later protocol version may support several operational Nostr identities under one BitLogin recovery root:

One recovery root
├── Personal identity
├── Business identity
└── Pseudonymous identity

That is a separate feature and requires explicit protocol design for:

  • Identity creation and deletion
  • Per-identity credential wrapping
  • Recovery and identity rotation
  • Connection Vault sharing or separation
  • Cross-identity privacy
  • Export and migration

The first account switcher must not imply that independently added accounts are recoverable from one phrase.

5. Local account registry

The client may keep an origin-scoped local registry containing only the data needed to render an account picker and locate the account's local session.

Example:

{
  "schema": "bitlogin.local-account-registry.v1",
  "accounts": [
    {
      "local_account_id": "local-random-id",
      "npub": "npub1...",
      "display_name": "Adam",
      "picture": "https://example.com/avatar.jpg",
      "last_used_at": 1784750000,
      "session_state": "unlocked"
    }
  ],
  "active_local_account_id": "local-random-id"
}

The registry must not store:

  • Login passwords
  • Recovery phrases
  • Password-derived keys
  • Recovery signing keys
  • Plaintext Connection Vault credentials
  • Plaintext private messages

Display metadata is a cache and may be rebuilt from the account's signed profile.

6. Session storage

Each account session must be namespaced by an opaque local account identifier. A single global currentSession record is insufficient.

Conceptual IndexedDB layout:

bitlogin
├── account-registry
├── sessions/<local-account-id-a>
├── sessions/<local-account-id-b>
└── active-account

Each session should contain only the minimum material already accepted by the BitLogin session-persistence model, independently encrypted or protected by the origin's storage boundary.

A local attacker with access to the browser profile may obtain every unlocked or persisted account session. The interface must state this limitation plainly.

7. Account states

An account may be:

unlocked     usable immediately
locked       remembered, but credentials must be entered
unavailable  local metadata exists, but remote login failed
removed      deleted from this device only

Removing an account from one device does not delete its relay capsules, rotate its identity, or revoke sessions on other devices.

8. Switching behavior

Switching accounts must be an explicit state transition:

  1. Pause subscriptions and relay cursors associated with the current account.
  2. Clear in-memory references to the current account's private keys and decrypted data from components that are not shared intentionally.
  3. Activate the selected account's worker and session namespace.
  4. Reconfigure the signer surface, relay lists, messaging state, Connection Vault, application state, and entitlements for the selected identity.
  5. Dispatch an account-change event to the host application.
  6. Refresh all account-dependent user interface.

The interface must never leave one account's profile visible while another account's signer is active.

9. Signer behavior

The single global window.nostr slot can represent only one active signer. Therefore:

  • Only the active account may own BitLogin's global signer surface.
  • Switching accounts must replace or rebind that surface atomically.
  • Element-scoped BitLogin instances should expose the active account explicitly.
  • Host applications must receive an account-change event and discard cached public keys, relay lists, permissions, and app state.

Suggested event:

window.addEventListener("bitlogin-account-changed", event => {
  console.log(event.detail.previousPubkey);
  console.log(event.detail.currentPubkey);
});

No private key or credential is included in the event.

10. User interface

Suggested account picker:

Accounts

● Adam
  npub1abc...xyz
  Current account

○ BitRoad
  npub1def...uvw
  Last used yesterday

○ Anonymous
  npub1ghi...rst
  Locked

[Add account] [Manage accounts]

Account-switch confirmation should be optional for ordinary switching but required when an application has an operation awaiting signature or payment.

The active account must remain visibly identifiable in:

  • Signer approval dialogs
  • Wallet and storage permission dialogs
  • Purchase and entitlement views
  • Message composition
  • Profile editing

11. Add-account flows

The client should support:

  • Sign into an existing BitLogin account
  • Recover an existing BitLogin account with its phrase
  • Create a new independent BitLogin account
  • Import an existing nsec and wrap it in a new BitLogin account

Adding an account must not silently log out or overwrite the current account.

12. Logout and removal

The interface should distinguish:

  • Lock account: clear active in-memory secrets but retain the remembered account and any permitted encrypted session state.
  • Log out account: clear that account's persisted session from this device.
  • Remove account from device: remove local registry metadata and session data.
  • Log out all accounts: clear every local BitLogin session.

None of these operations revoke a Nostr private key already exported elsewhere.

13. Privacy requirements

BitLogin should avoid creating remote records that publicly enumerate all accounts used on one device. The account registry is local by default.

Independent accounts must not share:

  • Locator identities
  • Recovery identities
  • Connection Vault roots
  • Relay queries on one authenticated relay connection
  • Application permission grants
  • Messaging cursors

Network timing may still correlate accounts used from the same device or IP. Separate relay connections and privacy-preserving transport remain recommended.

14. Application API

Conceptual API:

const accounts = await bitlogin.accounts.list();

await bitlogin.accounts.switch(accounts[1].localAccountId);

await bitlogin.accounts.lock(accounts[0].localAccountId);

await bitlogin.accounts.removeFromDevice(accounts[2].localAccountId);

The API must never return passwords, recovery phrases, private keys, or plaintext Connection Vault records.

15. Acceptance criteria

The initial feature is complete when:

  1. At least two independent BitLogin accounts can remain remembered on one device.
  2. Switching an unlocked account does not rerun Argon2id or fetch its capsule.
  3. Locking one account does not lock or corrupt another account.
  4. The signer, profile, relay configuration, messages, app state, connections, and entitlements always correspond to the visibly active account.
  5. A host app receives an explicit account-change notification.
  6. Removing an account deletes only its local state.
  7. Log out all accounts clears every persisted BitLogin session.
  8. No remote event exposes the device's account list.
  9. Cross-account storage keys, cursors, and relay connections remain isolated.
  10. Browser refresh restores the previously active account when its session is still available.
  1. Replace the single local session record with a namespaced session store.
  2. Add an account registry and active-account pointer.
  3. Add lock, logout, remove, and switch operations.
  4. Make the crypto worker lifecycle account-aware.
  5. Add host-application account-change events.
  6. Add account-aware relay, messaging, app-state, entitlement, and Connection Vault modules.
  7. Add tests for rapid switching, reload restoration, and cross-account leakage.