Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions bip-spdescriptor.mediawiki
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<pre>
BIP: spdescriptor
Layer: Applications
Title: Silent Payment Output Script Descriptors
Author: Craig Raw <[email protected]>
Comments-Summary: No comments yet.
Comments-URI: TBD
Status: Draft
Type: Informational
Created: 2025-12-03
License: BSD-2-Clause
Post-History: https://groups.google.com/g/bitcoindev/c/bP6ktUyCOJI
Requires: 44, 341, 350, 352, 380
</pre>

==Abstract==

This document specifies <tt>sp()</tt> output script descriptors for silent payments.
<tt>sp()</tt> descriptors take silent payment key material and describe P2TR outputs when combined with sender input public keys as defined in BIP352.

==Copyright==

This BIP is licensed under the BSD 2-clause license.

==Motivation==

BIP352 defines silent payments, a protocol for static payment addresses without on-chain linkability.
This descriptor provides a standardized way to represent silent payment outputs within the output descriptor framework, enabling wallet interoperability and backup/recovery using existing descriptor-based infrastructure.

==Specification==

A new top level script expression is defined: <tt>sp()</tt>.

===Key Expressions===

Two new key expression types are defined for use with <tt>sp()</tt> descriptors:

====<tt>spscan</tt>====

The <tt>spscan</tt> key expression encodes the scan private key and spend public key.
It is a [https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki Bech32m] encoding of:
* The human-readable part "spscan" for mainnet, "tspscan" for testnets
* The data-part values:
** The character "q", to represent silent payments version 0
** The payload: <tt>ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>P</sub>(B<sub>spend</sub>)</tt>

====<tt>spspend</tt>====

The <tt>spspend</tt> key expression encodes both the scan and spend private keys.
It is a [https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki Bech32m] encoding of:
* The human-readable part "spspend" for mainnet, "tspspend" for testnets
* The data-part values:
** The character "q", to represent silent payments version 0
** The payload: <tt>ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>256</sub>(b<sub>spend</sub>)</tt>

Note: The serialization of <tt>ser<sub>256</sub>(p)</tt> and <tt>ser<sub>P</sub>(P)</tt> follows the definition in BIP352.

===<tt>sp()</tt>===

The <tt>sp(KEY)</tt> or <tt>sp(KEY,BIRTHDAY,LABEL,...)</tt> expression can only be used as a top level descriptor.

<tt>sp(KEY)</tt> takes a single key expression as an argument, which must be either an <tt>spscan</tt> or <tt>spspend</tt> encoded key, optionally with key origin information.
If included, the key origin information should specify the derivation path to the account level as defined in BIP44.
When combined with sender input public keys, the descriptor produces P2TR output scripts describing silent payments made to wallets represented by the key expression.

When using the minimal form <tt>sp(KEY)</tt>:
* The birthday defaults to block height 842579 (May 8, 2024, when BIP352 was merged)
* Only the change label (<tt>m = 0</tt>) is assumed

<tt>sp(KEY,BIRTHDAY,LABEL,...)</tt> takes:
* A key expression (first argument)
* A birthday as a positive integer representing a block height (second argument)
* Zero or more label integers (remaining arguments), where each label is a positive integer

The birthday indicates the block height at which scanning should begin, and must be ≥ 842579.
Each label represents a label integer <tt>m</tt> used with the wallet, corresponding to <tt>B<sub>m</sub> = B<sub>spend</sub> + hash<sub>BIP0352/Label</sub>(ser<sub>256</sub>(b<sub>scan</sub>) || ser<sub>32</sub>(m))·G</tt> as defined in BIP352.
When labels are specified, only those specific label integers are scanned for (in addition to the change label <tt>m = 0</tt> which is always scanned).

The output scripts produced are BIP341 taproot outputs as specified in BIP352.

==Examples==

Valid descriptors:

* <tt>sp(spscan1q...)</tt> - Minimal form with default birthday and change label only
* <tt>sp([deadbeef/352'/0'/0']spscan1q...,900000)</tt> - With key origin and custom birthday
* <tt>sp(spspend1q...,842579,1,2,3)</tt> - With birthday and labels 1, 2, 3
* <tt>sp([deadbeef/352'/0'/0']spscan1q...,900000,1,5,10)</tt> - With key origin, birthday, and labels 1, 5, 10

Invalid descriptors:

* <tt>sp()</tt> requires a key expression
* <tt>sp(xpub...)</tt> requires spscan or spspend encoded key
* <tt>sp(spscan1q...,abc)</tt> birthday must be a positive integer
* <tt>sh(sp(spscan1q...))</tt> sp() is top level only
* <tt>wsh(sp(spscan1q...))</tt> sp() is top level only

==Usage Notes==

The change label (<tt>m = 0</tt>) should always be scanned for, even when not explicitly listed.
This ensures compatibility across different wallet implementations and prevents loss of funds from change outputs.

When recovering a wallet from a descriptor, scanning should begin at the specified birthday block height.
All labels specified in the descriptor must be scanned for when detecting payments.

For watch-only wallets, use <tt>spscan</tt> encoding.
For full wallets that can both scan and spend, use <tt>spspend</tt> encoding.

==Backwards Compatibility==

<tt>sp()</tt> descriptors use the format and general operation specified in [[bip-0380.mediawiki|380]].
As this is a wholly new descriptor, it is not compatible with any prior implementation.
The scripts produced are BIP341 taproot outputs, making them indistinguishable from other taproot outputs on-chain.

==Reference Implementation==

TBD

==Test Vectors==

TBD