Would people be happy with this way of automatically generating SP addresses for any npub out there?
### The address generation, end to end
**Inputs**
- `npub_pubkey`: 32-byte x-only secp256k1 point (BIP-340 convention, implicit even-y)
- `npub_privkey`: the corresponding 32-byte scalar (recipient only)
**Sender side (anyone with the npub)**
```
P_base = lift_x(npub_pubkey) // 33-byte point, even-y
tweak = int(tagged_hash("nip-XX/sp-scan", npub_pubkey)) mod n
B_spend = P_base // 1:1 with the npub
B_scan = P_base + tweak · G
```
Optional override: if the recipient has published a kind 10336 event carrying a hardened `B_scan_hardened`, use that instead of the derived `B_scan`. Senders that always paid the unhardened address before will keep matching, because the recipient's hardened wallet also runs detection against the unhardened `b_scan`.
**Address encoding (BIP-352 standard)**
```
payload = version(0x00) || compress(B_scan) || compress(B_spend) // 1 + 33 + 33 = 67 bytes
sp_address = bech32m("sp", payload) // → "sp1q…"
```
**Recipient side**
```
b_spend = npub_privkey // = identity privkey
b_scan = (npub_privkey + tweak) mod n // unhardened
or
b_scan = int(tagged_hash("nip-XX/sp-scan-hardened", npub_privkey)) // hardened (override)
```
**Parity / lift_x note.** The npub is x-only; SP math is on full points. Convention: always `lift_x` with even-y. The per-output key `P_k = B_spend + t_k · G` is the *internal* taproot key — the output script uses its x-only form (`P_k`'s x-coordinate as the taproot output key, no script path). Standard BIP-341 normalization applies.
That's the whole address spec: one tagged-hash tweak, one point addition, one bech32m encoding. No new event kinds required for the default case; one optional replaceable event for the safe-delegation override.
---
### Privacy features
- **On-chain unlinkability.** Every received output is a fresh-looking taproot key. A chain analyst with the npub can compute the SP address and *still* cannot identify any output as belonging to it (CDH-hard without `b_scan`). The recipient's income stream is invisible from chain alone.
- **No sender-recipient interaction.** Recipient can be offline indefinitely; sender pays using only the npub. No address handshake, no LNURL callback, no relay roundtrip required to pay.
- **Address is publicly safe to share.** Publishing the SP address (or letting it be derived from the npub) leaks nothing on-chain — it's just two public points, and observing them doesn't break CDH.
- **Light-client scanning preserves recipient privacy.** Tweak-index servers serve generic per-block public data computed from chain only; the recipient runs the final match locally with `b_scan`. The indexer never sees the scan key or learns which outputs are the recipient's.
- **Hint events (gift-wrapped) decouple discovery from public linkage.** The on-nostr receipt for fast detection is sealed with NIP-17, so relay observers see neither the sender, the recipient, nor the txid.
### Privacy limitations
- **The address is a public 1:1 function of the npub.** Anyone who knows your npub can compute your SP address — and therefore the social graph "this npub has an on-chain payment surface" is fully public. There is no unlinkable subaddress per payer; the trade for 1:1 is loss of identity-vs-address separation. (BIP-352 *labels* could provide per-payer subaddresses, but using them means each subaddress needs its own out-of-band publication, breaking the 1:1.)
- **The unhardened scheme makes scan delegation unsafe.** `b_scan` minus the public tweak equals `npub_privkey`. Any wallet shipping this MUST refuse to export the unhardened scan key. Safe delegation requires the recipient to publish a hardened override and hand out *that* `b_scan_hardened` — a manual opt-in step users will mostly skip.
- **Public hint events would destroy the on-chain privacy.** Gift-wrapping is not optional. If a client implementation defaults to public `["p", recipient_npub]`-tagged hint events, anyone querying relays can reconstruct the full receipt graph from txids in those events. This is the single biggest implementation footgun in the design.
- **Sender always learns one output.** Whoever made a payment knows exactly which output they created — including its key, amount, and txid. Standard for any payment system; mentioned for completeness. They cannot extrapolate to *other* payments the same recipient received.
- **Tweak-index queries leak metadata at the IP layer.** Even though the server doesn't see `b_scan`, it sees "this IP is requesting block tweaks." Mitigations are network-level (Tor, mixing, batching) and outside the spec.
- **On-chain amounts are public.** Bitcoin baseline. Once a tx is located (by anyone who learns the txid through whatever channel), the amount is visible. SP hides *which* tx, not *what was in it*.
- **Timing correlation across nostr and chain.** Even with gift-wrapped hints, an adversary watching both relays and the mempool can correlate "encrypted nostr event T+Δs after tx confirms" to link payments to recipients statistically. Mitigations are batching/delays/cover traffic, with the usual cost.
- **Override rotation is cache-sensitive.** Republishing kind 10336 with a new `B_scan_hardened` creates a window where senders see different versions; require `valid_from` plus an overlap period during which both keys remain detectable. Otherwise in-flight payments can land on a key the recipient no longer scans for.
### One-line summary
A nostr npub becomes an `sp1…` address via one tagged-hash tweak — sender-derivable, recipient-private, on-chain unlinkable, and nostr-unlinkable as long as hint events are gift-wrapped. The two structural costs are: (1) the npub→address mapping is public, so the address can't double as a private identity, and (2) the unhardened scan key is identity-equivalent, so scan delegation requires the opt-in hardened override.
Login to reply
Replies (17)
interesting, so, silentpayments adapted for nostr? progress
Yep, I still dont know what to put in the zap event, though. Because if I put the transaction, anyone can trace those UTXOs to the keys. If I don't anyone can fake zaps..
decouple the events. have it send a nip17 giftwrap dm "sent you a payment". if the payments are faked, or valid, allow the client to publish attestations for the sender/receiver npub like a commerce rating.
The problem is that the sender and receiver collude. And the amount being public can allow for a time collition attack: just look for transactions on the block with similar value around the same time to find the transaction id and identify it.
It's already easy to fake zaps. Just have two npubs zapping each other. I do it for testing, between @Silberengel and @Laeserin all the time.
Public zap receipts for non-trivial amounts are overrated. I do think that small-ish public zaps are a strong signal and give people a chance to advertise without pissing everyone off, as they send the ad along with some cash.
The differentiation for public+small versus private+big makes sense to me, as it's like the difference between throwing some coins into a donation basket and transferring $500 to cover rent.
Money transfers are more privacy-sensitive when they are larger. I think we all naturally sense that.
Love the general idea, and I'll try to read it all in detail soon
-----
I think the main challenge will be not about deriving the SP code, but more about whether nostr relays are sufficiently reliable that recipients will get the encrypted hint each time
-----
You mention an event sent after the tx confirms. Why wait until the confirmation?
Why not either send it immediately, or with a short random delay to avoid timing-based analysis?
---------
Are there SP(-like) schemes where you can give a little bit of private information to a scanner such that the scanner can find your transactions but scanner can't steal the funds. My goal is to make things very light on the client side, so that semi-trusted scanning services can do the heavy lifting. (I'm asking this out of technical curiously; not because I think it's the right privacy trade-off)
On the last point, this scheme offers both hardened and unhardened setups. By default it is unhardened and cannot be shared with anyone otherwise bad stuff happens. But users could add a hardned key to use with any service.
why would the amount be public? why would collusion between a few npubs be enough to infiltrate an entire WOT attestation set if you have your own set of ratings?
Because all clients display the zap amount publicly, even on private zaps today.
No. I still don't want my identity credentials connected to my financial ones. If we build a way to disconnected them we don't need any nostr-related key derivation, we just use traditional on-chain methods.
@npub1uaj9...665u knows best if that will work 👀
That method does allow you to add a new event with a Silent Payment key that everywone will use instead of the default one that is linked to your nsec.
So, you can opt out.. essentially.
But by default everybody gets a way to receive without any additional work.
Can we have a way of saying "heres how to get an address" instead?
Thats the thing we want to avoid.. We do that today with multiple lightning solutions. We have tried cashu, custodial wallets, etc... nothing sticks. We need something that works out of the box.
yeah, and i think this is wrong, unwanted, and has led to a place where zapping is completely unsuccessful endeavor as a feature.
i have already removed amount from my private zaps and am in the process of also removing bolt11 from my nwc server. as i keep saying, these things were HUGe mistakes.
Oh thanks, I've just learned from you that the problem I describe in my final point has already been solved in the existing SP protocol.