From 0fbeb3c55413fb221d890b3bf3464ebd2aed9fa1 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 28 Jun 2026 09:31:51 -0700 Subject: Locked gifts: let NIP-07 extension users decrypt in-place, skip the code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gift payload is now encrypted two ways in the link: under the one-time code (the DM / manual path) AND to the recipient's nostr pubkey via an ephemeral key. When a NIP-07 browser extension (window.nostr) is present, the claim view offers 'Claim with nostr extension' — it checks the extension's pubkey matches the gift, decrypts ctKey via window.nostr.nip44.decrypt, and claims into a fresh wallet with no code. The pasted-code path remains as the fallback for everyone else. Verified: both decrypt paths recover the payload from the blob; a different extension account is rejected. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/wallet.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/wallet.js') diff --git a/src/wallet.js b/src/wallet.js index b2ff3e3..e2b5b35 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -23,7 +23,7 @@ import { concatBytes } from '@scure/btc-signer/utils.js'; import { Api, pool, getBackend, explorerWeb, electrumCandidates, spIndexerUrl } from './api.js'; import { ElectrumApi } from './electrum.js'; -import { NostrSync, getSyncConfig, encryptWithCode, npubOf } from './nostr.js'; +import { NostrSync, getSyncConfig, encryptGiftPayload, npubOf } from './nostr.js'; import { isSilentPaymentAddress, decodeSilentPaymentAddress, silentPaymentScripts, silentPaymentPlaceholder, deriveSilentPaymentKeys, encodeSilentPaymentAddress, silentPaymentScan, silentPaymentOutputPrivKey, silentPaymentCandidate, bloomHas } from './silentpay.js'; import { schnorr } from '@noble/curves/secp256k1'; @@ -2579,8 +2579,8 @@ const LOCKED_GIFT_VERSION = 1; // to the recipient via a nostr DM; the public link carries only the ciphertext. // Returns { blob (for the /lg/ link), claimCode (to DM) }. export function lockGift(code, amount, recipientPkHex) { - const { code: claimCode, ct } = encryptWithCode(code); - const blob = base64urlnopad.encode(new TextEncoder().encode(JSON.stringify({ v: LOCKED_GIFT_VERSION, amount, to: recipientPkHex, ct }))); + const { code: claimCode, ctCode, eph, ctKey } = encryptGiftPayload(code, recipientPkHex); + const blob = base64urlnopad.encode(new TextEncoder().encode(JSON.stringify({ v: LOCKED_GIFT_VERSION, amount, to: recipientPkHex, ct: ctCode, eph, ctKey }))); return { blob, claimCode }; } // The public fields of a locked-gift blob (or null). Decryption needs the code. -- cgit v1.2.3