The inverse of requestNwcConnection: an app that obtained an NWC URI by its own means (its own wallet chooser, its own paste box) offers the user a portable copy. The credential is already in the app's hands, so the only question on screen is whether a copy enters the user's vault -- and that write happens in BitLogin's own chrome or not at all, never silently, because a page that can pump records into someone's account without a tap is a spam vector wearing a feature's clothes. Dedupe by connection identity (wallet pubkey + secret; relays and labels may drift without making a new connection -- sameNwcCredential in core): a duplicate resolves 'already-saved' with a silent origin-binding refresh and no UI, which is what makes the call safe to fire after every successful connect. 'unavailable' (no session, no vault root) is a quiet no-op by contract. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CMr9Yt8SYmZcfgrg3CV2G1
6.4 KiB
Changelog
Tracks changes to @bitlogin/core and @bitlogin/widget that matter to a
site with its own vendored copy (see "Updating your integration" in
README.md). Not every commit needs an entry — only ones where a site
sitting on an older vendored copy is missing a fix or behavior change it
would plausibly want. Newest first.
2026-07-30
- Widget:
requestNwcConnection()— the Connection Vault authorization layer (reveal mode). Apps ask for a wallet; the widget owns sign-in, one-tap approval for an origin-bound connection, or guided import via Bitcoin Connect's chooser (lazy chunks — never downloaded unless the flow opens) or a pasted URI, stored as an encrypted vault record. Dashboard gains a "Wallet connections" management screen (revoke app access vs remove-from-BitLogin, honestly distinguished). The vault root now rides the encrypted session cache, so restored sessions can use connections without re-login; the sudo key is never cached. New deploy note: the widget now ships FIVE files (two extra lazy chunks) that must travel together, and hosts using the wallet flow needstyle-src-attr 'unsafe-inline'in their CSP. - Widget:
offerNwcConnection()— offer-to-save for app-obtained wallets. The inverse ofrequestNwcConnection: an app that got an NWC URI through its own chooser or paste box can offer the user a portable copy. Consent-gated in the widget's own UI, never silent (the write goes to the user's account); duplicates by wallet+secret resolvealready-savedwith a silent origin-binding refresh and no UI, so apps can fire it after every successful connect. - Connection Vault protocol layer in
@bitlogin/core(src/vault/): encrypted per-connection records signed by a derived vault identity, the finalized §CV6 key hierarchy with pinned test vectors, the NWC credential profile (lossless URI parse/export), relay sync with quorum readback and per-record rollback detection, and the two-tier model — connectable records vs sudo-gated personal records (connection-vault.md§18). - Capsules gained paired optional fields
connection_vault_rootandvault_sudo_key(both or neither; validated). Registration mints them for new accounts;enableConnectionVaultis the phrase-gated, fail-closed, idempotent migration for existing accounts. Login, password change, and phrase recovery all carry them forward. Integration caveat: a site on an OLDER vendored widget that performs a password change will rebuild the credential capsule WITHOUT these fields, stranding connectable records until a phrase ceremony repairs them — update vendored copies before enabling the vault for users.
2026-07-21
-
Fix: registration/login could fail quorum if a single built-in relay was down.
BUILTIN_VAULT_RELAYShad only 3 relays andBUILTIN_DISCOVERY_RELAYSonly 2, sharingrelay.nostr.bandbetween both lists.publishAndVerify()requires a fixedminAcks/minReadbacksof 2 regardless of relay count, so losing that one relay left zero margin — surfacing as "Registration did not reach the required relay acknowledgement and readback quorum." Addednostr.wineandrelay.snort.socialto the vault list, andnostr-pub.wellorder.netto discovery. (681f622) Re-vendor if: your site uses the default built-in relay list (i.e. you don't pass your ownvault-relays/discovery-relaysattributes). If you already pass your own relay list, this doesn't affect you. -
Fix: widget completely unstyled (giant logo, unstyled buttons) under a strict
style-srcCSP.element.tsinjected its CSS as an inline<style>tag inside the shadow root on every render. A host withstyle-src 'self'and no'unsafe-inline'silently drops an inline<style>'s rules entirely — visible as the brand SVG rendering at its raw ~590×119px intrinsic size instead of the intended 20px-tall lockup, with every other shadow-DOM element similarly falling back to unstyled browser defaults. Switched to building the stylesheet once as aCSSStyleSheetand assigning it viaadoptedStyleSheets, which isn't subject tostyle-src. (e8712f9) Re-vendor if: your site's CSP setsstyle-srcwithout'unsafe-inline'(check your CSP header/meta tag). If yourstyle-srcalready includes'unsafe-inline', or you have no CSP, this was invisible to you either way, but re-vendoring is still a good idea (this fix also drops needless per-render CSS reparsing). -
Feature: NIP-04 support, for drop-in parity with a real NIP-07 extension.
window.nostr.nip04(and the element-scopednip04Encrypt/nip04Decrypt) didn't exist at all before this — only NIP-44 was implemented. A site (or its own legacy DM code path) callingwindow.nostr.nip04.encrypt(...)while BitLogin was the active signer gotCannot read properties of undefinedinstead of a working call. (2838402) Re-vendor if: your site (or any third-party code running on it) useswindow.nostr.nip04— a real NIP-07 extension always has it, so anything written against "whateverwindow.nostrprovides" may already assume it's there. No attribute/config changes needed; this is purely additive.
2026-07-22
- Feature: sessions persist across a page reload. Previously every reload
destroyed the crypto worker, requiring the full login-name + password flow
again — re-running Argon2id and a relay quorum read on every navigation,
not just the first sign-in. After a successful register/login/recover/
changePassword, the everyday private key and active capsule events are now
cached in IndexedDB;
connectedCallback()tries to restore that cache before ever showing the welcome screen, firingbitlogin-loginexactly as a fresh login would if one is found. Logout clears it. (9e3787d) Security note: this means the decrypted everyday private key now sits in the browser's IndexedDB for that origin until logout — the same tradeoff every NIP-07 extension and crypto wallet already makes for the same "don't ask for the password every navigation" reason, scoped by IndexedDB's own per-origin isolation. The recovery phrase and login name/password are never cached. Re-vendor if: you want this — it's the reason to re-vendor this entry. No attribute/config or host-integration changes needed; a host page's existingbitlogin-loginlistener (see "Embedding BitLogin" below) fires the same way whether the sign-in was just typed or silently restored.