Wallets and Privacy
← Topics

Silent Payments

A short tour of BIP-352 — static, reusable Bitcoin addresses that don't compromise your privacy.

Give one silent payment address — printed on a card, pinned to a profile, hosted on a donation page. Senders derive a fresh on-chain output every time. The address never appears on-chain.

THE PROPOSAL
BIP-352. josibake, Ruben Somsen & Sebastian Falbesoner. Assigned 9 March 2023, Status Complete. Builds on the 2014 stealth-address lineage (Peter Todd's bitcoin-dev post, sx).
WHERE WE ARE
Sparrow 2.5.0 ships receive support. Several wallet projects are working on scanning at scale. 2140's annual report covers the year of progress.

Why Reuse Hurts

The two ways we receive today, and why neither is great.

REUSE ONE ADDRESS
Anyone who's ever paid you can see who else paid you, how much, and when. Address reuse compromises you and the parties who transact with you.
FRESH ADDRESS EACH TIME
Bob can't pay Alice whenever he wants. He has to ask for an address, wait for Alice to respond, and only then send. The friction adds up.
enter
silent payments. One static sp1... address. The sender derives a unique on-chain output every time, with no back-and-forth. The address never touches the chain.

How It Works

Four moves. Most of the work lives in step 4.

1
Receiver creates a reusable silent payment address — encoded from two pubkeys: Bscan and Bspend. Publishes it once.
2
Sender picks the UTXOs they'll spend, combines their inputs with the silent payment address, and derives a unique Taproot output for the receiver.
3
Sender builds and broadcasts the transaction. On-chain it looks like any other Taproot payment — no flag, no marker.
4
Receiver scans every new block, re-deriving the addresses money could have been sent to. (Easier said than done — more on slide 7.)

Encoding the Address

Two pubkeys, one static address. Encoded once by the receiver, decoded by every sender.

silent payment address (published once, never on-chain) sp1... = bech32m( "sp", v || Bscan || Bspend ) "sp" human-readable prefix · v version byte (currently 0x00) · || byte concatenation

The encoding is reversible: anyone who reads sp1... can bech32m-decode it to recover both Bscan and Bspend.

SCAN KEYPAIR
bscan (priv) ↔ Bscan (pub)
The receiver uses this to detect incoming payments (next slide — ECDH).
SPEND KEYPAIR
bspend (priv) ↔ Bspend (pub)
The receiver uses this to claim them — tweaked into the on-chain output (slide after).

ECDH: The Shared Secret

Two parties land on the same number without ever talking. Silent payments is built on this.

elliptic-curve Diffie–Hellman, in three lines
Alice has a private key a and publishes A = a · G. Bob has b and publishes B = b · G. G is a fixed generator point on the curve.

Alice computes a · B; Bob computes b · A. Both land on the same point: a · B = a · (b · G) = ab · G = b · (a · G) = b · A. Scalar multiplication is commutative.

An eavesdropper sees only A and B. Recovering a from A would mean solving the elliptic curve discrete log problem — computationally hard. Without a or b, no shared secret.
1 silent payments plugs the sender's UTXO keypair + receiver's scan keypair into ECDH

Both a and A are the keypair of the UTXO the sender is spending in this transaction. The sender already has a; the receiver gets A from that input's pubkey once the tx is broadcast — that's when the ECDH symmetry closes.

Output and Detection

S is the seed. The spend keypair gets tweaked into the on-chain output and the claim key.

2 sender derives the on-chain output and broadcasts
on-chain output pubkey — sender uses Bspend (decoded from sp1...) and S (from step 1) Pk = Bspend + hash(S, k) · G
3 receiver scans each new block, looking for a match
receiver's scan loop, per transaction
for k = 0, 1, 2, ...:
    candidate = Bspend + hash(S, k) · G   # receiver's own B_spend — same formula as the sender's P_k
    if candidate equals one of the tx's output pubkeys:   # equality check
        skk = bspend + hash(S, k)   # receiver's own b_spend, same tweak → claim privkey
    else:
        stop   # no more outputs to this sp1... in this tx; one sp1 can receive several outputs per tx, hence the loop
why scan and spend are separate keypairs
Scanning recomputes bscan · A = S per tx, then candidate = Bspend + hash(S, k) · G — so it needs bscan and Bspend. You can hand both to a less-trusted scanner (watching server, indexer, hot device) and it can detect payments without ever being able to spend. Only bspend stays cold; it comes out once a match is found, to sign the claim.

About That Tradeoff

Receivers do a lot more work. Senders do almost the same thing they always did.

THE COST
For every eligible transaction in every new block (one with a Taproot output and at least one input the receiver could derive a shared secret from), the receiver does one ECDH to compute the shared secret S, plus one more elliptic-curve multiplication per candidate output index it checks. Compare to a normal BIP-32 wallet, which just looks up incoming payments in a list of known addresses - silent payments trade that lookup for ongoing math.
THE SHORTCUTS
Skip dust. Skip transactions from before the wallet was created. A node that stays up-to-date only has to scan ~one new block every 10 minutes — manageable. The hard problem is the first-time backfill, and light-client support.
WALLET SUPPORT TODAY
Sparrow 2.5.x ships silent-payment receive. Cake Wallet, BlueWallet and others are in active development. The 2140 annual report covers the year's scanning-infrastructure progress.
OPEN QUESTIONS
How do light clients participate? Can servers do the heavy index work without compromising privacy? What's the right UX for the moment a payment lands without an explicit "address request"?