3adfe5d89a
The HD wallet can already derive its own sp1q… receive address and detect
incoming silent payments via the BlindBit indexer, but the Send dialog
only handled bare Bitcoin addresses (or npubs / nprofiles, which routed
through nostrPubkeyToBitcoinAddress). Silent-payment funds were stuck:
they showed in the balance but the dialog gated them with a "spending
isn't supported yet" notice.
Now the dialog handles both ends:
- Recipient resolution in parseHdRecipient accepts sp1… (mainnet, v0)
alongside bc1…, npub1…, and nprofile1…. The send mutation decodes the
address, derives the per-transaction P_k locally from the selected
inputs' BIP-341-tweaked private keys, and writes it as a regular P2TR
output. The on-chain transaction looks like any other Taproot spend;
the BIP-352 ECDH happens entirely off-chain.
- The coin selector now mixes BIP-86 UTXOs with SP UTXOs from the
NIP-78 storage doc. SP inputs are signed by computing
d_k = b_spend + t_k and writing tapKeySig directly, bypassing
@scure/btc-signer's automatic TapTweak (which would re-tweak the
already-on-chain P_k and produce an invalid signature).
- The "silent-payment-only balance" warning is gone — those funds are
now spendable. The privacy disclaimer still appears for bare bc1…
addresses but is suppressed for sp1… recipients, since the whole
point of silent payments is that the on-chain output is fresh and
unlinkable.
src/lib/hdwallet/sp/sender.ts contains the BIP-352 sender math (address
bech32m decode, outpoint serialisation, ECDH, P_k derivation) ported from
Ditto and adapted to noble-curves v2. src/lib/hdwallet/sp/spend.ts holds
the spend-side helpers (b_spend derivation, d_k = b_spend + t_k, manual
Schnorr signing for SP inputs). Both are covered by the BIP-352 canonical
taproot-only test vectors plus a sender↔receiver round-trip check that
the same (b_spend, t_k) the scanner persists really does produce the
P_k the spender signs against.