diff options
| author | Adam Soltys <[email protected]> | 2026-08-01 21:56:02 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-01 21:56:02 -0700 |
| commit | 5dbd42fa330da23924335d8287b01bd23b1fcff9 (patch) | |
| tree | bb0bfaa9717d08d21886d5070a0d7257c54533b3 /src/features/nostrlogin.js | |
| parent | 2d1cce8b6b0e7454f187e7144d22f76466065528 (diff) | |
| download | coinosv3-5dbd42fa330da23924335d8287b01bd23b1fcff9.tar.gz coinosv3-5dbd42fa330da23924335d8287b01bd23b1fcff9.zip | |
One wallet per Nostr identity, NIP-98 auth, npub payment addresses
Consistency fix with teeth: logging in with a pasted key derived one
wallet while an extension login for the SAME identity found no backup
and generated a different one. Now every signer kind runs one flow —
look for the published association first (that's where the money is),
then derive (key in hand) or generate (signer only) — and a derived
seed is published too. That costs nothing, since the derivation is
public and anyone holding the key could compute it anyway, and it makes
the identity open the same wallet however you log in.
Registrar auth is now NIP-98 (HTTP Auth, kind 27235) instead of a
homemade envelope: the signature binds the URL, method and body hash,
so a captured header can't be replayed elsewhere. NIP-78 already
covered the encrypted association; NIP-26 delegation is deprecated, so
owner→manager nomination lives in the registrar's own model: an
npub-shaped name may only be claimed by that identity, which nominates
the wallet key to keep the record updated afterwards (the wallet key is
the only one always available — offline, in the worker).
Payment addresses therefore default to the user's REAL npub in all
three login methods, falling back to the wallet's own identity when no
Nostr account is linked.
Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src/features/nostrlogin.js')
| -rw-r--r-- | src/features/nostrlogin.js | 27 |
1 files changed, 23 insertions, 4 deletions
diff --git a/src/features/nostrlogin.js b/src/features/nostrlogin.js index 60e3759..c5369e9 100644 --- a/src/features/nostrlogin.js +++ b/src/features/nostrlogin.js @@ -22,6 +22,11 @@ export function nostrLoginFeature(ctx) { const load = () => wallet.loadFeatureState('nostrlogin', {}); const save = (st) => wallet.saveFeatureState('nostrlogin', st); + // The signer that opened this session, kept so the names feature can claim + // the user's real npub as their payment address (and nominate the wallet + // key as manager, so later updates work without the signer). + let live = null; + const busy = (v) => { ui.nostrLoginBusy = v; render(); }; const fail = (e) => { ui.nostrLoginError = e.message || String(e); busy(false); }; @@ -40,11 +45,16 @@ export function nostrLoginFeature(ctx) { busy(false); return; } - await ctx.hook('openMnemonic', res.mnemonic, res.passphrase || '', { nostrPubkey: signer.pubkey }); + // Same wallet however you log in: a key-derived seed is published too, + // so an extension login later finds this wallet instead of making a new + // one. Costs nothing — anyone with the key could derive it anyway. + if (res.publish) await publishWalletBackup(signer, { mnemonic: res.mnemonic }).catch(() => {}); + live = signer; + await ctx.openMnemonic(res.mnemonic, res.passphrase || '', { nostrPubkey: signer.pubkey }); + save({ ...load(), pubkey: signer.pubkey, linked: Date.now() }); toast(res.mode === 'derived' ? t('nlOpenedDerived') : t('nlOpenedRestored')); busy(false); } catch (e) { fail(e); } - finally { if (signer && signer.close && !ui.nostrLoginNew) signer.close(); } } async function createForSigner() { @@ -55,7 +65,9 @@ export function nostrLoginFeature(ctx) { const mnemonic = newMnemonic(); await publishWalletBackup(st.signer, { mnemonic }); ui.nostrLoginNew = null; - await ctx.hook('openMnemonic', mnemonic, '', { nostrPubkey: st.signer.pubkey }); + live = st.signer; + await ctx.openMnemonic(mnemonic, '', { nostrPubkey: st.signer.pubkey }); + save({ ...load(), pubkey: st.signer.pubkey, linked: Date.now() }); toast(t('nlCreated')); busy(false); } catch (e) { fail(e); } @@ -75,11 +87,11 @@ export function nostrLoginFeature(ctx) { throw new Error(t('nlAlreadyLinked')); } await publishWalletBackup(signer, { mnemonic: wallet.mnemonic, passphrase: wallet.passphrase || '' }); + live = signer; save({ ...load(), pubkey: signer.pubkey, linked: Date.now() }); toast(t('nlLinked')); busy(false); } catch (e) { fail(e); } - finally { if (signer && signer.close) signer.close(); } } // ---- UI --------------------------------------------------------------- @@ -157,6 +169,13 @@ export function nostrLoginFeature(ctx) { return { id: 'nostrlogin', + // The nostr identity this session is logged in as, for features that + // should speak as the user (the payment address defaults to this npub). + nostrLoginIdentity() { + const st = load(); + if (!st.pubkey) return null; + return { pubkey: st.pubkey, npub: npubOf(st.pubkey), signer: live }; + }, unlockExtra() { return unlockExtra(); }, settingsCards() { return [settingsCard()]; }, }; |
