Silent Payments: A Technical Guide to BIP 352

bitshala

3. August 2026August 3, 2026 · 36 Min. Lesezeit36 min read

Bitcoin has a well-known hygiene rule: never reuse an address. Every time a receiver hands out the same address twice, they hand an observer a free clustering heuristic. Balance, payment history, counterparty count, and eventual spend destination all become visible to anyone with a block explorer.

d = (b_spend + hash(input_hash·b_scan·A || 0)) mod n

The standard fix is to generate a fresh address per payment. That works fine when the two parties can talk to each other - a point-of-sale terminal, a checkout flow, an invoice. It fails completely when they can't. A donation page, a GitHub funding file, a Nostr profile, a tip jar printed on a sticker: these are all cases where the receiver publishes one identifier and has no channel back to the sender. The available workarounds are all bad. Reuse a single address and accept the privacy loss. Publish an xpub and let anyone enumerate your entire wallet. Run a server that hands out fresh addresses, and now you have infrastructure to operate, secure, and keep online, plus a server that knows exactly who asked for which address.

BIP 352 removes the choice. A silent payment address is a static string that senders can use indefinitely, and every payment made to it lands on a distinct, freshly derived Taproot output. There is no notification transaction, no extra on-chain data, and nothing that identifies the payment as a silent payment at all. On-chain it is indistinguishable from any other P2TR spend.

The cost is real and it falls entirely on the receiver: because outputs are derived from data the sender chooses, the receiver cannot know in advance what to look for, and must scan the chain to find their own money. That single tradeoff shapes every design decision in the protocol and most of the engineering work still outstanding.

This guide covers the cryptographic construction, the specification details that implementations get wrong, the scanning problem and its current architectural answers, the PSBT and descriptor layer that makes hardware signing possible, and how BIP 352 compares to the two proposals it competes with. It assumes familiarity with elliptic curve arithmetic, BIP 32 derivation, and Taproot.

Notation. Uppercase letters are public keys, lowercase are the corresponding private keys, · is scalar multiplication, || is byte concatenation, G is the secp256k1 generator, and n is the curve order. hash_tag(x) denotes a BIP 340 tagged hash: SHA256(SHA256(tag) || SHA256(tag) || x).

Prior art: where do you put the notification?#

Non-interactive static addresses are not a new idea, and the design space is narrower than it looks. Every proposal has to solve the same problem - the receiver needs to learn that a payment exists and how to derive its key - and the proposals differ mainly in where they put that signal.

Peter Todd's original stealth address proposal embedded signalling data in every payment. This worked but was expensive and made the payments trivially identifiable as stealth payments, which is its own privacy problem: a small anonymity set is worse than none.

BIP 47 (Reusable Payment Codes), which shipped as "PayNyms" in Samourai and later Sparrow and Stack Wallet, moved the signal out of the payment and into a one-time setup step. Before paying Bob for the first time, Alice broadcasts a notification transaction to a static notification address Bob publishes, carrying a blinded copy of her own payment code in an OP_RETURN. Bob decrypts it, derives the range of addresses Alice will use, and from then on watches them like any ordinary BIP 32 wallet - cheap scanning, works with stock Electrum servers and BIP 158 filters.

The costs are substantial. Every first-time payment requires two on-chain transactions, which makes one-off payments expensive. The notification transaction is a public, permanent, structurally obvious record that some entity intends to pay a specific payment code, and it pays to a reused static address. It creates a "toxic" output that wallets have to actively hide from coin selection to avoid linking Alice's wallet to Bob's. Because every subsequent payment from Alice follows the derivation path established by her notification, Bob can link all of Alice's payments to each other - sender privacy is worse than with a plain address. And since Alice's own payment code travels in the notification, a compromised or malicious receiver can retroactively deanonymize her entire BIP 47 history if she ever publicly ties that code to her identity.

BIP 351 (Private Payments), proposed in 2022 by Alfred Hodler and Clark Moody, splits the difference. It still uses a notification transaction, but derives a unique one-time notification address per sender instead of reusing a static one, which removes the address-reuse linkage. Receivers scan only OP_RETURN outputs rather than reconstructing input keys, which is far cheaper than BIP 352 scanning and easier to delegate to a third party. But the two-transaction minimum remains, and the notification still stands out on-chain.

Silent Payments, proposed by Ruben Somsen in March 2022 and specified as BIP 352 with josibake and Sebastian Falbesoner, takes the remaining option: put the signal nowhere. The insight is that a Bitcoin transaction already contains everything needed to establish a shared secret between sender and receiver - the sender's input public keys are published as a necessary consequence of spending. Nothing needs to be added. The receiver reconstructs the shared secret from data that was going to be on-chain anyway.

The notification cost doesn't vanish; it converts from a space cost paid once by the sender into a computation and bandwidth cost paid continuously by the receiver. Whether that trade is worth it is the central question in evaluating BIP 352, and reasonable engineers disagree.

Deriving the protocol#

The specification is best understood as a sequence of fixes, each closing a hole in the previous version. Following that sequence makes every term in the final formula legible.

Naive ECDH#

Bob publishes a public key B. Alice, spending a UTXO with private key a and public key A, computes:

P = B + hash(a⋅B) ⋅ G

and pays to P encoded as a BIP 341 output. Since a·B == b·A, Bob can recompute the same tweak using his private key and Alice's published input key. He scans candidate transactions, computes B + hash(b·A)·G, and checks whether the result appears among the outputs.

This is the whole idea. Everything that follows is hardening.

Multiple outputs to the same recipient

One output per recipient per transaction is limiting. Alice may want to split a payment across several outputs to imitate a CoinJoin or to obscure her change amount; two participants in a collaborative transaction may both want to pay Bob. So a counter is introduced:

Pk = B + hash(a ⋅ B ∣∣ k)⋅G, k=0,1,2,...

Bob searches for P_0. If and only if he finds it does he check for P_1, then P_2, stopping at the first miss. Because the extra work only happens after a confirmed hit, the marginal scanning cost is negligible. Output ordering within the transaction is irrelevant.

This introduces an obligation on the sender that is easy to violate: every output generated must appear in the final transaction. Drop P_1 from a set of three and Bob will never find P_2, because he stops incrementing at the first gap.

Preventing forced address reuse

If Alice pays Bob twice from different UTXOs that happen to share the same public key A, both payments derive the same P_k. The protocol would have reintroduced address reuse. The fix is to bind the derivation to something unique per transaction:

input_hash = hash(outpoint || A)
P_0 = B + hash(input_hash·a·B || 0)·G

Committing to A as well as the outpoint is not decorative. Without it, a malicious sender could pick a private key a' for a later transaction such that a' = input_hash·a / input_hash', making the shared secret collide and forcing address reuse on the receiver.

Using all inputs#

Real transactions have many inputs. Rather than nominating one, the protocol sums them:

a = a_1 + a_2 + ... + a_n
input_hash = hash(outpoint_L || A)   where A = a·G
P_0 = B + hash(input_hash·a·B || 0)·G

outpoint_L is the lexicographically smallest outpoint in the transaction. Three separate benefits follow from summing, and they are worth stating individually because they explain why this is not merely a convenience:

Scanning cost. If the sender used an arbitrary input, the receiver would have to test every input of every candidate transaction - one EC multiplication per input. Summing means one 33-byte aggregate key and one EC multiplication per transaction.

Light client feasibility. That single 33-byte value per transaction is what makes serving tweak data to light clients tractable at all. It is the unit of bandwidth in every light-client architecture discussed later.

CoinJoin sender privacy. If Alice used one of her own inputs, she would be telling Bob which input in the CoinJoin is hers, degrading the round's anonymity set. Summing hides it - though as we will see, using silent payments in CoinJoins has unresolved problems.

Choosing the smallest outpoint rather than, say, hashing all of them keeps the derivation independent of final input ordering and lets memory-constrained hardware signers compute the tweak without holding the whole transaction. Note the endianness quirk: outpoints are hashed in their transaction serialization order, which is little-endian, unlike the rest of the BIP. This lets a wallet parse a serialized transaction directly without byte reordering.

Splitting scan and spend authority#

So far Bob needs b online to detect payments. That is unacceptable for cold storage. The protocol therefore splits Bob's identity into two keys, and he publishes both:

P_0 = B_spend + hash(input_hash·a·B_scan || 0)·G

Bob's online scanner holds b_scan and the public B_spend, and computes P_0 = B_spend + hash(input_hash·b_scan·A || 0)·G. Spending requires b_spend, which never leaves the signing device, and the output's private key is:

d = (b_spend + hash(input_hash·b_scan·A || 0)) mod n

This is the structural feature that makes silent payments compatible with cold storage: the scan key is a detection capability, not a spending capability. Compromising a scanning server leaks the receiver's entire payment history but no funds.

Labels

Bob wants to distinguish incoming payments - which donation came from which campaign, which invoice was paid. Publishing multiple independent addresses would multiply his scanning cost linearly, which is prohibitive. Instead he tweaks the spend key with an integer:

B_m = B_spend + hash(b_scan || m)·G,  m = 1, 2, 3, ...

and publishes (B_scan, B_1), (B_scan, B_2), and so on. All of these share B_scan, so a single scan covers all of them. Detection works by subtraction: Bob computes the unlabelled P_0, subtracts it from each transaction output, and checks whether the remainder matches a label tweak he has used.

Labels are not identities. Every labelled address exposes the same B_scan, so any observer can trivially group them as belonging to one entity. Labels tell Bob where a payment came from; they do not give him separate personas. Implementations that market labelled addresses as distinct identities are misleading their users.

m = 0 is reserved for change. This gives a receiver a way to recognize their own change outputs after restoring from seed alone, without BIP 32 change chains. A wallet must never hand out the m = 0 address, or third parties could send payments that the wallet misclassifies as its own change. Even wallets that don't implement labels should always scan for the change label, since that costs almost nothing and maximizes cross-wallet recoverability.

The specified form

With tagged hashes and explicit serialization, the sender's derivation is:

a           = a_1 + ... + a_n              (Taproot keys negated for odd Y)
A           = a·G
input_hash  = hash_BIP0352/Inputs(outpoint_L || A)
shared      = input_hash·a·B_scan
t_k         = hash_BIP0352/SharedSecret(serP(shared) || ser32(k))
P_k         = B_m + t_k·G

The receiver computes shared = input_hash·b_scan·A from the summed input keys and derives the same t_k. To spend, the private key is d = (b_spend + t_k + label_tweak) mod n, where label_tweak is hash_BIP0352/Label(ser256(b_scan) || ser32(m)) if a label applies and zero otherwise.

image Both parties arrive at the same shared secret from different starting material: the sender from its own private keys, the receiver from the input public keys the transaction publishes anyway. The published address is the only thing that ever passes between them.

The specification: what implementations get wrong#

The mathematics above is the easy part. Nearly all of the difficulty in shipping BIP 352 lives in serialization rules, input eligibility, and control flow. This section covers the details that cause interoperability failures and, in the worst cases, unrecoverable funds.

Address encoding and forward compatibility

A silent payment address is a Bech32m string with HRP sp for mainnet and tsp for test networks, a version character, and a 66-byte payload consisting of serP(B_scan) || serP(B_m). Version 0 uses the character q, producing addresses that begin sp1q.

Two encoding details break naive implementations. First, the 90-character limit from BIP 173 does not apply. Be careful with the exact figure: the BIP's stated minimum of 117 characters assumes a three-character HRP, which is the testnet case. A mainnet v0 address is 116 - two characters of HRP, one separator, one version character, 106 characters of payload (ceil(66·8/5)), and six of checksum. An implementation that hardcodes 117 as a minimum will reject every mainnet address it is shown. Since future versions may extend the payload, allow up to 1023 characters at the top end, and validate the decoded payload length rather than the string length wherever possible.

Second, the version scheme is designed for forward compatibility and must be implemented as specified, because it determines whether a v0 wallet can still pay a v1 recipient:

image

The truncate-and-proceed rule for v1–v30 is what lets a future version add fields without orphaning older senders. v31 is deliberately reserved as the escape hatch for a change that genuinely cannot be made backwards compatible. Getting this wrong doesn't break anything today, which is exactly why it tends to get skipped - and it will break the next upgrade.

Which inputs count

Any UTXO can fund the transaction. Only these types may contribute to shared secret derivation:

Only compressed and x-only keys are permitted. Inputs with conditional branches or multiple keys - anything CHECKMULTISIG-shaped - are excluded, and the reason is malleability in collaborative settings. If script-y inputs contributed keys, a participant in a CoinJoin could help derive the output using one key set and then rebroadcast with signatures over a different set, leaving the receiver unable to find funds that were legitimately paid to them. When a single entity controls all inputs this is not a threat, but the rule has to hold universally.

Two consequences bind the sender's coin selection: at least one input must be an eligible type, and inputs spending SegWit version > 1 outputs must be excluded entirely.

Taproot: three traps

Taproot inputs are where most implementation bugs cluster.

Always use the output key. For both key path and script path spends, the key used for ECDH is the Taproot output key - the tweaked key in the scriptPubKey - not the internal key. This matters for coin selection: to spend a Taproot input in a silent payment transaction, the sender must hold the private key for the output key. If they only have script path authority, the input cannot be used.

Do not skip script path spends. It is tempting to handle only key path spends. Doing so reopens the CoinJoin malleability hole: an attacker joins a round, participates in deriving the output with a key path spend, then broadcasts using the script path. A receiver who ignored script path inputs would compute a different sum and miss the payment. There is exactly one exception - inputs whose Taproot internal key is the NUMS point H are skipped. The receiver detects this by checking the internal key in the control block. This carve-out exists so that use cases where the key path is provably unusable can still participate in silent payment transactions without obstructing them.

Negate private keys with odd Y. Under BIP 340 every x-only public key has two private keys, d and n - d. The receiver, summing x-only keys, implicitly assumes even Y. The sender must therefore check each Taproot input's private key and negate it if it produces an odd-Y point before summing. Skip this and the shared secret silently diverges - the transaction is valid, the money is gone from the sender's wallet, and the receiver never finds it. This is the single most expensive bug possible in the protocol.

Parsing P2PKH scriptSigs

For P2PKH inputs the receiver extracts the public key from the scriptSig - and must do so even when the scriptSigdoes not match the standard template. A construction like <dummy> OP_DROP <sig> <pubkey> is third-party malleable, so a receiver that only handles the canonical form can be griefed out of detecting a payment by anyone who rewrites the scriptSig in flight. Parse for the key; don't pattern-match the script.

Scanning eligibility

A transaction must be scanned if and only if all three hold:

  1. It has at least one BIP 341 output.
  2. It has at least one input from the eligible list.
  3. It does not spend any output with SegWit version > 1.

The third condition is the upgrade path, and it is worth understanding why it exists. If a future SegWit version arrives and silent payments v1 adds support for it, wallets should not have to scan the same transaction twice under two different rule sets. Excluding unknown-version inputs today means v0 and v1 scanning domains stay disjoint. Note that this restriction applies only to inputs.

An optimization the spec explicitly permits: transactions with no unspent Taproot outputs can be skipped when the goal is finding current balance rather than full history.

Sighash constraints

SIGHASH_ANYONECANPAY is unsafe and must not be used. The derivation commits to the sum of input keys, so if inputs can change after signing, the receiver's reconstruction fails and the payment becomes undetectable. Implementations should use SIGHASH_ALL, or SIGHASH_DEFAULT for Taproot inputs. SIGHASH_SINGLE and NONE are permitted but offer no advantage here.

The same logic applies to RBF: if a fee bump adds or removes inputs, every silent payment output must be re-derived. A wallet that reuses the original output scripts when bumping will broadcast a transaction paying keys the receiver cannot compute. The BIP calls this out as a required functional test, which is a good sign of how often it gets missed.

How silent payments will interact with proposed sighash flags such as SIGHASH_GROUP and SIGHASH_ANYPREVOUT is an open question.

The scanning loop, and the rule that must not be optimized

Receiver-side scanning, once eligibility passes:

  1. Sum the eligible input public keys to get A. If A is the point at infinity, skip the transaction.
  2. Compute input_hash, then shared = input_hash·b_scan·A.
  3. Collect all Taproot output keys into outputs_to_check.
  4. From k = 0: compute t_k and P_k = B_spend + t_k·G. Compare against each candidate output. On a match, record it, remove that output from the candidate set, and rescan the remaining candidates with k+1.
  5. On a non-match, compute label = output - P_k and check it against the wallet's known label tweaks - always including the change label m = 0. If no label matches, negate the output and repeat the check.
  6. Stop when no match is found at the current k, or when k reaches K_max.
image

The two branches most often omitted are the negation retry and the path back to COMP after a label match. Both are marked here because skipping either loses money silently.

Two things in that loop deserve emphasis.

The negation retry is mandatory, not defensive. Taproot output keys are x-only, so the Y coordinate is unknown and the comparison fails roughly half the time for purely representational reasons. An implementation that omits the second check with the negated output will miss approximately 50% of payments made to it. This is not a rare edge case; it is a coin flip on every payment.

The decision to increment k must depend only on whether a cryptographic match occurred - never on whether the wallet considers the output useful. BIP 352 carries an explicit warning about this. If a wallet finds P_0, decides it's below its dust threshold, and stops scanning, it will never discover P_1 and P_2 from the same sender. Filtering by wallet policy is fine; filtering before incrementing is a fund-loss bug. Apply policy after the cryptographic loop completes, not inside it.

K_max and the quadratic scanning attack

Version 1.1.0 of the BIP, published in March 2026, introduced a per-group recipient limit K_max = 2323. The attack it closes is worth understanding because it illustrates the shape of DoS risk in scan-based protocols.

Under current consensus rules a single block can hold roughly 23,255 P2TR outputs. An adversary can construct one transaction filled with that many outputs, all targeting the same scan key. Without a cap, the victim's scanning loop is O(N²) in the number of outputs - each successful match triggers a rescan of the remaining candidate set - which takes minutes of CPU per block on modern hardware. Capping the inner loop at K_max reduces the worst case to O(N·K_max), on the order of tens of seconds.

The specific value is not arbitrary: 2323 is the maximum number of P2TR outputs that fit in a 100,000 vByte transaction, so any transaction that respects current standardness rules is guaranteed to stay under the limit. Honest senders are never affected, and there is no fingerprintable behaviour change. Senders must fail if a recipient group exceeds K_max; receivers must stop scanning at it.

Key derivation, backup, and recovery

The BIP recommends - but does not mandate - BIP 32 derivation with purpose 352:

scan_private_key:  m / 352' / coin_type' / account' / 1' / 0
spend_private_key: m / 352' / coin_type' / account' / 0' / 0

Both paths must use hardened derivation. This is a security requirement, not a style preference: hardened derivation is what makes it safe to hand the scan private key to a scanning server or another device without exposing the master key or the spend key. Using unhardened derivation here would make the scan key a lever on the rest of the wallet.

Recovery is where silent payments differ most sharply from BIP 32 wallets, and where wallet authors should think carefully:

The scanning problem#

Everything hard about silent payments is downstream of one asymmetry: sending is trivial and receiving is not.

A sending wallet needs a new address parser, a coin selection constraint, one ECDH operation, and correct handling of Taproot key parity. That is a modest patch, and it is why the BIP authors and the ecosystem consistently urge wallet developers to ship sending support first - a sending-only wallet costs little and immediately makes every published sp1address payable.

Receiving requires the wallet to examine every eligible transaction since its birthday. There is no set of scriptPubKeys to hand to a server, no Bloom filter to register, no gap limit. This is a structural break with the Electrum model, in which a client tells a server exactly what to watch for. A silent payments client cannot do that - not because the interface is missing, but because the thing it would need to hand over is scanning capability itself. The client does not know its own addresses until it has done the work, and the only way to delegate the work is to delegate the scan key. Every architecture below is a different answer to that constraint.

The bandwidth arithmetic

The unit of work is the 33-byte aggregate tweak per eligible transaction. A full node - or any index server backed by one - can precompute input_hash·A for every eligible transaction and serve it. Clients then need one EC multiplication per transaction to test it.

The BIP's own figures, based on data from January 2023 through July 2024:

image

Three filters do the heavy lifting in getting from the worst case to the practical case. Non-Taproot outputs are excluded by definition. Dust outputs at or below 1,000 sats can be skipped, which at the time of the silentpayments.xyz analysis covered roughly 85% of Taproot outputs. And transaction cut-through exploits the fact that a client interested only in current balance need not consider transactions whose Taproot outputs have all been spent since the last scan. Empirically about 75% of transactions with at least one non-dust unspent Taproot output have spent all of them within 150 blocks. A client scanning daily or every few days requests dramatically less than one scanning every block.

Note the direction of travel: these numbers get worse as Taproot adoption rises. The 30–50 MB/month figure is a function of Taproot being a minority of outputs. A future in which most outputs are P2TR is a future in which silent payments scanning is meaningfully more expensive. Anyone planning capacity should model the trend, not the snapshot.

Two-stage scanning with BIP 158

Tweak data alone tells a client what keys to look for; it doesn't tell it whether those keys appear in a block. BIP 158 compact block filters close the gap: derive the candidate outputs, test them against the block filter, and fetch the full block only on a hit.

This is efficient but not fully resolved. The BIP notes that it may be possible to request only the prevout txids and vouts for transactions with Taproot outputs, plus scriptPubKeys and amounts - enough to build a spending transaction without downloading the block. What that does to BIP 158's security assumptions is explicitly flagged as an open question. Meanwhile, sourcing the tweak data itself in a trustless manner remains the core unsolved problem: a client that accepts tweaks from a server is trusting that server not to omit transactions, and omission is a silent failure.

Out-of-band notifications

The spec sketches an optional escape valve. A sender can transmit an encrypted notification - encrypted to the receiver's scan public key - containing the spend public key (which communicates the label), the shared secret portion hash(shared || k), and the outpoint and amount so the output is immediately spendable.

The design deliberately keeps these non-authoritative. A receiver can always fall back to scanning, and should, because malicious notifications enable three distinct failures: claiming a payment that doesn't exist (resolvable probabilistically via block filters, or definitively by fetching the block); reporting a wrong outpoint or amount, causing failed spends or fee overpayment; and supplying a valid key that was not derived per the protocol, which would make the output unrecoverable from seed backup. That last one is the dangerous one - an implementation must never accept such an output, and detecting it requires checking against real tweak data.

The BIP's authors are candid about why notifications aren't part of the protocol proper: making them mandatory would relocate the privacy and cost problems rather than solving them, add a delivery-failure and censorship surface, and make notification data backup-critical. A sensible wallet design treats a notification as a hint - mark the payment unconfirmed, display it, and promote it once scanning verifies it.

Three architectures

Deployments fall into three categories, and the distinction is about who holds the scan key, which is the whole privacy story:

image What crosses the trust boundary is the whole difference between these three.

Full node scanning. The wallet has its own node and scans locally. Maximum privacy, no third party, and an initial sync measured in hours rather than minutes - reported figures for a full chain scan on reference hardware run in the range of 8–12 hours. Fine for a server-side donation processor, a non-starter for a phone. Node stack support is also still thin.

Tweak servers. The server indexes and serves 33-byte tweaks per eligible transaction; the client performs its own ECDH and matching. The server never learns which outputs belong to which client - it cannot, because it never sees a scan key. This is the architecture that preserves BIP 352's privacy properties for light clients, and it is what the "privacy-preserving scanning" column on silentpayments.xyz tracks.

Remote scanners. The client hands its scan key to a server, which does the scanning and reports results. Bandwidth and CPU on the client collapse to nearly nothing, background sync and push notifications become possible, and the UX matches an ordinary wallet. The cost is precise: that server learns every payment the user receives, forever.

The privacy comparison is not simply "worse," and it is worth being careful here because this is where most commentary gets sloppy. A remote-scanner user still has full privacy against chain observers, which a reused address does not provide. They have traded chain-level privacy for a named, contractual trust relationship - which is the same trade the majority of Bitcoin light wallets already make with Electrum servers. That may be entirely rational for a small-balance mobile wallet and a bad idea for an organisation whose donor list is sensitive. What matters is that wallets say plainly which model they are running.

Frigate: the remote-scanner approach in production

The remote-scanner model stopped being hypothetical in May 2026. Sparrow 2.5.0 shipped silent payments receiving wallets backed by Frigate, an Electrum server extended with silent-payments scanning methods, with the public instance at frigate.2140.dev auto-selected when an SP wallet is in use. It is worth looking at in detail because it makes concrete choices the BIP leaves open, and because it is the strongest existing evidence about what BIP 352 receiving can feel like.

Frigate's design rationale is an explicit critique of Appendix A. The compact-block-filter approach works for full nodes but, on its reading, does not work for light clients: a reasonable scan period requires gigabytes of tweaks, filters, and blocks to be downloaded, parsed, and cached, against the few megabytes a traditional Electrum wallet needs to restore. It also offers no mempool visibility at all. So Frigate moves scanning server-side, in line with the Remote Scanner profile in the BIP 352 Index Server Specification.

Three design details are interesting beyond Sparrow:

Subscription replaces the gap limit. Frigate proposes new Electrum JSON-RPC methods in which a client subscribes to a silent payment address, and servers advertise the capability through a silent_payments field in the server.featuresresponse. The subscription is the direct analogue of the monotonically increasing derivation index in a BIP 32 wallet - it is how the client learns about incoming payments it could not have predicted. This is the cleanest available answer to the "there is no gap limit" problem, and standardizing it in the Electrum protocol would let any wallet adopt the model.

Scanning is pushed into the database, with optional GPU acceleration. The elliptic curve work runs inside the datastore rather than in application code. This is what makes historical scans interactive rather than something you start and walk away from.

Mempool ingestion. Frigate tracks the mempool in real time, so users see incoming payments before confirmation. This deserves more attention than it gets: "did you get my transaction?" is a basic expectation, and a pure tweak-server-plus-block-filter client structurally cannot answer it well. Protocol-level reorg handling is specified too - the server rescans subscriptions from the reorged height and pushes any newly found history.

What this does and does not settle. It settles the UX question. A desktop user can now run a silent payments receiving wallet, with air-gapped hardware signing, that behaves like a normal wallet - and can self-host Frigate if they would rather not trust 2140(.)dev. That is a genuine milestone and the single biggest practical change in the ecosystem since the BIP merged.

It does not settle the problem the BIP flags as open. Frigate is a remote scanner: it holds scan keys. The unsolved research question was never "can scanning be made fast" - it was "can a light client scan without granting anyone scanning capability." Server-side scanning is fast precisely because it sidesteps that question. Both statements are true at once: receiving silent payments is now practical for most users, and trustless light-client scanning remains unsolved. Conflating the two overstates the state of the art, and it also obscures the residual risk that survives the trust trade - a remote scanner can still withhold a transaction, and now it knows everything as well.

PSBT, hardware signers, and descriptors#

The base BIP is silent on how a transaction gets constructed when the entity deriving outputs isn't the entity holding keys. That gap is why silent payments were initially unusable with hardware wallets and multisig, and closing it took a family of follow-on BIPs.

The core incompatibility is structural: a silent payment output script cannot be computed until every input is known, because it depends on the sum of input keys and the smallest outpoint. PSBTv0 freezes the unsigned transaction at creation. Even PSBTv2 (BIP 370) assumes each output has a known script.

BIP 375 - Sending Silent Payments with PSBTs (merged January 2025) resolves this by making PSBT_OUT_SCRIPToptional and introducing PSBT_OUT_SP_V0_INFO, serialized as a zero version byte followed by the 33-byte scan key and 33-byte spend key. An output carrying only PSBT_OUT_SP_V0_INFO is a silent payment whose script hasn't been computed yet; an output carrying both has been resolved. There is a subtlety in unique identification: since a PSBT is equally valid with or without the computed script, using PSBT_OUT_SCRIPT for the identifier would make the ID malleable, so implementations must always prefer PSBT_OUT_SP_V0_INFO when present. BIP 375 also carries the DLEQ proof fields that let one signer prove to other parties that its ECDH share was computed honestly - necessary for multisig and for MuSig2/FROST setups where the ECDH must be performed collaboratively as a sum of partial shares. The proof format itself is specified separately in BIP 374. For the common case of a single entity holding all input keys, DLEQ generation is unnecessary.

BIP 376 - Spending Silent Payment Outputs with PSBTs handles the other direction. A hardware signer asked to spend a previously received silent payment output needs the tweak, because the signing key is d = (b_spend + tweak) mod n and the device cannot derive the tweak without having scanned. BIP 376 adds PSBT_IN_SP_TWEAK, which the Updater must include when an input spends a silent payment output, and PSBT_IN_SP_SPEND_BIP32_DERIVATION to locate the spend key - settable to a zero fingerprint if the updater prefers not to reveal derivation details. The total overhead is on the order of 413 bytes per input. It was split from BIP 375 on the reasoning that sending and spending belong to the sender's and receiver's spheres respectively.

BIP 392 - Silent Payment Output Script Descriptors, authored by Sparrow's Craig Raw, defines an sp() descriptor so silent payment wallets fit into existing descriptor-based backup, recovery, and interoperability tooling. sp(KEY,KEY) takes a scan key expression - which must be a private key, since scanning requires it - and a spend key expression, which may be any BIP 380 single-key expression including musig(). Uncompressed keys are prohibited throughout, matching BIP 352. The proposal introduces spscan1... and spspend1... Bech32m key formats, and allows a wallet birthday as an optional second argument plus label integers as further arguments, with m = 0 implicit. Whether new key formats are warranted, versus simply sp(scankey,spendkey) with existing formats, was actively debated on the mailing list - worth reading if you are implementing descriptor support, since this is not settled.

The practical upshot for hardware: signers can send and can sign spends, but cannot receive. Continuous block scanning is not something an air-gapped device with a small screen and no network connection will ever do. The realistic architecture pairs a hardware signer with a software wallet that handles scanning, and PSBT is the interface between them.

Comparing the three proposals#

image The three proposals differ in where the notification lives. BIP 47 and BIP 351 put a beacon on-chain and get cheap scanning in return; BIP 352 puts nothing on-chain and pays for it in scanning cost.

image

Open problems#

An honest account of BIP 352 has to be clear about what is unfinished. The specification is marked Complete - the protocol is stable and implementable - but several surrounding questions are not resolved.

Trustless light client tweak sourcing. This is the big one, and the BIP says so directly: Appendix A is explicitly framed as motivating research rather than specifying a solution. A light client that accepts tweak data from a server has no way to detect omission. Proposals include a new BIP 157/158 filter type for tweak data, which has not been started in Bitcoin Core. As the Frigate section below covers, the practical UX problem now has a production answer in the remote-scanner model, but that answer works by granting the server scanning capability - it does not touch this problem. Until it is solved, "privacy-preserving" light client scanning means the server cannot see your outputs, not that it cannot withhold them.

CoinJoin. Silent payments are designed with collaborative transactions in mind, but there is no formal security proof for the protocol in a collaborative setting, and the BIP recommends against it. The mechanics also need a blinding scheme: if participants collaborate to derive the output, they learn who is being paid, so Blind Diffie–Hellman is required to prevent that. This works on paper; it is not deployed.

MuSig2 and FROST. Any Taproot output capable of a key path spend is theoretically supported, with participants computing partial ECDH shares that sum to the shared secret, and DLEQ proofs to keep them honest. The same caveat applies - no security proof unless all participants are the same entity or mutually trusted.

Future sighash flags. How silent payments could work with SIGHASH_GROUP or SIGHASH_ANYPREVOUT is unresolved, and the tension is fundamental rather than incidental: the derivation commits to a fixed input set, and these flags exist to make input sets flexible.

Address expiry. Peter Todd argued in 2023 that silent payment addresses should carry an expiration time, on the grounds that an address published permanently creates an unbounded scanning obligation and an unbounded window in which a stale key can receive funds. The current spec has no expiry field. The debate is live and is a plausible candidate for a v1 payload extension.

No amount, memo, or payment metadata. A silent payment address carries two public keys and nothing else. No amount, no description, no expiry, no proof of payment. Compare BOLT 12 offers, which carry rich invoice semantics. In practice this is handled by composing with other layers - BIP 353 DNS-based payment instructions can map a human-readable name to a silent payment address, which is a good pairing since both target the "publish one identifier" use case - but it is a real functional gap relative to Lightning-era expectations.

Rising cost with Taproot adoption. Discussed above, and worth restating as an open problem rather than a footnote: the scanning burden is proportional to eligible Taproot activity, and that is going up.

On-chain only. Silent payments are not usable for Lightning channel funding or invoice payment. They address the on-chain static-address problem and nothing else.

Where to start#

If you are adding silent payments to a wallet, the ordering is not a matter of taste:

Ship sending first. It is a small change, it carries no scanning burden, and every sending implementation makes the whole ecosystem more useful immediately. The BIP was designed to make sending easy specifically so this would be true.

Run the test vectors, then the functional tests. The BIP ships JSON test vectors and a Python reference implementation. Passing the vectors is necessary and not sufficient - the BIP separately enumerates functional tests that catch the failures vectors miss:

Add two of your own that the BIP implies but doesn't list: verify that Taproot private keys with odd Y are negated before summing, and verify that k increments on a cryptographic match even when the matched output is filtered by wallet policy. Both are silent fund-loss bugs.

Test with mixed inputs and mixed outputs. Include ineligible input types to confirm they are excluded from derivation, and include ordinary non-silent-payment outputs to yourself to confirm silent payment scanning doesn't interfere with normal output detection.

Decide your scanning architecture before you build the UI, because it determines what you can honestly promise users. Local node scanning, tweak server, and remote scanner have materially different privacy properties, and the choice is not reversible without a migration.

References#

Specifications#

Implementations and background#

**This article was written by @theanmolsharma_ for @bitshala_org.*

#28536](https://github.com/bitcoin/bitcoin/issues/28536)

Written by

Comments

No comments yet. Start the conversation.

Contents

12 sections

  1. Prior art: where do you put the notification?
  2. Deriving the protocol
  3. Naive ECDH
  4. Using all inputs
  5. Splitting scan and spend authority
  6. The specification: what implementations get wrong
  7. The scanning problem
  8. PSBT, hardware signers, and descriptors
  9. Comparing the three proposals
  10. Open problems
  11. Where to start
  12. References