Amber now signs Bitcoin transactions too
View quoted note →
Vitor Pamplona
_@vitorpamplona.com
npub1gcxz...nj5z
Nostr's Chief Android Officer - Amethyst Social
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.
To the Math wiz out there, the ideal solution would be an SP address that can be derived from the pubkey directly in such a way that nobody needs to "set it up" with a supporting client.
The goal is to send money to any npub without having to rely on whatever they are using as a client.
Of course, that would only make sense if the same derivation blocks the public from combining all silent payments to an npub into a single balance.
Zap events then would have to contain a proof of a bitcoin transaction without identifying it directly. Otherwise, anyone would simply merge all Zap Events to find somebody's balance on chain.
Let's hope somebody can find a solution here.
Maybe this time around we will actually fix our longstanding UX issues in Nostr. We will see.
Let's just hope one of new Silent Payments NIPs are not worse than coordinating payments via NWC or having to trust a mint or a monitor.
We need to do this again every friday
View quoted note →
"Nostr feels live again" is the mood we need for mondays.