From 9557c1e2f68da553aa805050f7b7496f2a4b0f3d Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 3 Dec 2025 12:58:42 +0200 Subject: [PATCH 1/2] Add sp() output descriptor format for BIP352 Silent Payments --- bip-spdescriptor.mediawiki | 121 +++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 bip-spdescriptor.mediawiki diff --git a/bip-spdescriptor.mediawiki b/bip-spdescriptor.mediawiki new file mode 100644 index 0000000000..62221c4062 --- /dev/null +++ b/bip-spdescriptor.mediawiki @@ -0,0 +1,121 @@ +
+  BIP: spdescriptor
+  Layer: Applications
+  Title: Silent Payment Output Script Descriptors
+  Author: Craig Raw 
+  Comments-Summary: No comments yet.
+  Comments-URI: TBD
+  Status: Draft
+  Type: Informational
+  Created: 2025-12-03
+  License: BSD-2-Clause
+  Requires: 352, 380
+
+ +==Abstract== + +This document specifies sp() output script descriptors for silent payments. +sp() 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: sp(). + +===Key Expressions=== + +Two new key expression types are defined for use with sp() descriptors: + +====spscan==== + +The spscan 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: ser256(bscan) || serP(Bspend) + +====spspend==== + +The spspend 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: ser256(bscan) || ser256(bspend) + +Note: The serialization of ser256(p) and serP(P) follows the definition in BIP352. + +===sp()=== + +The sp(KEY) or sp(KEY, BIRTHDAY, LABEL, ...) expression can only be used as a top level descriptor. + +sp(KEY) takes a single key expression as an argument, which must be either an spscan or spspend 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 sp(KEY): +* The birthday defaults to block height 842579 (May 8, 2024, when BIP352 was merged) +* Only the change label (m = 0) is assumed + +sp(KEY, BIRTHDAY, LABEL, ...) 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 m used with the wallet, corresponding to Bm = Bspend + hashBIP0352/Label(ser256(bscan) || ser32(m))·G as defined in BIP352. +When labels are specified, only those specific label integers are scanned for (in addition to the change label m = 0 which is always scanned). + +The output scripts produced are BIP341 taproot outputs as specified in BIP352. + +==Examples== + +Valid descriptors: + +* sp(spscan1q...) - Minimal form with default birthday and change label only +* sp([deadbeef/352'/0'/0']spscan1q..., 900000) - With key origin and custom birthday +* sp(spspend1q..., 842579, 1, 2, 3) - With birthday and labels 1, 2, 3 +* sp([deadbeef/352'/0'/0']spscan1q..., 900000, 1, 5, 10) - With key origin, birthday, and labels 1, 5, 10 + +Invalid descriptors: + +* sp() requires a key expression +* sp(xpub...) requires spscan or spspend encoded key +* sp(spscan1q..., abc) birthday must be a positive integer +* sh(sp(spscan1q...)) sp() is top level only +* wsh(sp(spscan1q...)) sp() is top level only + +==Usage Notes== + +The change label (m = 0) 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 spscan encoding. +For full wallets that can both scan and spend, use spspend encoding. + +==Backwards Compatibility== + +sp() 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 + From 6807d2f4efc02d3533834c3278248e2db1be0f87 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 4 Dec 2025 10:53:13 +0200 Subject: [PATCH 2/2] Update headers and remove space after comma in descriptors --- bip-spdescriptor.mediawiki | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bip-spdescriptor.mediawiki b/bip-spdescriptor.mediawiki index 62221c4062..2f2f22aed2 100644 --- a/bip-spdescriptor.mediawiki +++ b/bip-spdescriptor.mediawiki @@ -9,7 +9,8 @@ Type: Informational Created: 2025-12-03 License: BSD-2-Clause - Requires: 352, 380 + Post-History: https://groups.google.com/g/bitcoindev/c/bP6ktUyCOJI + Requires: 44, 341, 350, 352, 380 ==Abstract== @@ -56,7 +57,7 @@ Note: The serialization of ser256(p) and serP ===sp()=== -The sp(KEY) or sp(KEY, BIRTHDAY, LABEL, ...) expression can only be used as a top level descriptor. +The sp(KEY) or sp(KEY,BIRTHDAY,LABEL,...) expression can only be used as a top level descriptor. sp(KEY) takes a single key expression as an argument, which must be either an spscan or spspend 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. @@ -66,7 +67,7 @@ When using the minimal form sp(KEY): * The birthday defaults to block height 842579 (May 8, 2024, when BIP352 was merged) * Only the change label (m = 0) is assumed -sp(KEY, BIRTHDAY, LABEL, ...) takes: +sp(KEY,BIRTHDAY,LABEL,...) 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 @@ -82,15 +83,15 @@ The output scripts produced are BIP341 taproot outputs as specified in BIP352. Valid descriptors: * sp(spscan1q...) - Minimal form with default birthday and change label only -* sp([deadbeef/352'/0'/0']spscan1q..., 900000) - With key origin and custom birthday -* sp(spspend1q..., 842579, 1, 2, 3) - With birthday and labels 1, 2, 3 -* sp([deadbeef/352'/0'/0']spscan1q..., 900000, 1, 5, 10) - With key origin, birthday, and labels 1, 5, 10 +* sp([deadbeef/352'/0'/0']spscan1q...,900000) - With key origin and custom birthday +* sp(spspend1q...,842579,1,2,3) - With birthday and labels 1, 2, 3 +* sp([deadbeef/352'/0'/0']spscan1q...,900000,1,5,10) - With key origin, birthday, and labels 1, 5, 10 Invalid descriptors: * sp() requires a key expression * sp(xpub...) requires spscan or spspend encoded key -* sp(spscan1q..., abc) birthday must be a positive integer +* sp(spscan1q...,abc) birthday must be a positive integer * sh(sp(spscan1q...)) sp() is top level only * wsh(sp(spscan1q...)) sp() is top level only