Coverage Report

Created: 2026-10-07 13:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/common/bip352.h
Line
Count
Source
1
// Copyright (c) 2023 The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#ifndef BITCOIN_COMMON_BIP352_H
6
#define BITCOIN_COMMON_BIP352_H
7
8
#include <addresstype.h>
9
#include <attributes.h>
10
#include <compat/byteswap.h>
11
#include <key.h>
12
#include <primitives/transaction.h>
13
#include <pubkey.h>
14
#include <uint256.h>
15
#include <util/expected.h>
16
17
#include <array>
18
#include <compare>
19
#include <cstdint>
20
#include <cstring>
21
#include <functional>
22
#include <map>
23
#include <memory>
24
#include <optional>
25
#include <span>
26
#include <string>
27
#include <variant>
28
#include <vector>
29
30
struct secp256k1_silentpayments_label;
31
struct secp256k1_silentpayments_prevouts_summary;
32
struct secp256k1_pubkey;
33
class CChainParams;
34
class CScript;
35
class Coin;
36
37
namespace bip352 {
38
39
using PubKey = std::variant<CPubKey, XOnlyPubKey>;
40
41
class PrevoutsSummary
42
{
43
private:
44
    std::unique_ptr<secp256k1_silentpayments_prevouts_summary> m_prevouts_summary;
45
46
public:
47
    PrevoutsSummary(const secp256k1_silentpayments_prevouts_summary& prevouts_summary);
48
    PrevoutsSummary(PrevoutsSummary&&) noexcept;
49
    PrevoutsSummary& operator=(PrevoutsSummary&&) noexcept;
50
    ~PrevoutsSummary();
51
52
    // Delete copy constructors
53
    PrevoutsSummary(const PrevoutsSummary&) = delete;
54
    PrevoutsSummary& operator=(const PrevoutsSummary&) = delete;
55
56
    const secp256k1_silentpayments_prevouts_summary* Get() const LIFETIMEBOUND;
57
};
58
59
struct BIP352Comparator {
60
63
    bool operator()(const COutPoint& a, const COutPoint& b) const {
61
        // BIP352 defines the "smallest outpoint" based on a lexicographic
62
        // sort of the outpoints, using the 36-byte serialization:
63
        // <txid, 32-bytes little-endian>:<vout, 4-bytes little-endian>
64
63
        if (a.hash != b.hash) return a.hash < b.hash;
65
20
        return internal_bswap_32(a.n) < internal_bswap_32(b.n);
66
63
    }
67
};
68
69
struct SilentPaymentsDestination
70
{
71
private:
72
    uint8_t m_version;
73
    CPubKey m_scan_pubkey;
74
    CPubKey m_spend_pubkey;
75
    std::vector<unsigned char> m_extension_data;
76
77
    SilentPaymentsDestination(
78
        uint8_t version,
79
        const CPubKey& scan_pubkey,
80
        const CPubKey& spend_pubkey,
81
        std::span<const unsigned char> extension_data = {}
82
132
    ) : m_version(version), m_scan_pubkey(scan_pubkey),
83
132
        m_spend_pubkey(spend_pubkey),
84
132
        m_extension_data(extension_data.begin(), extension_data.end()) {};
85
public:
86
    static std::optional<SilentPaymentsDestination> From(
87
        const CPubKey& scan_pubkey,
88
        const CPubKey& spend_pubkey,
89
        uint8_t version = 0,
90
        std::span<const unsigned char> extension_data = {}
91
    );
92
93
5
    uint8_t GetVersion() const { return m_version; }
94
4.76k
    const CPubKey& GetScanPubKey() const { return m_scan_pubkey; }
95
4.79k
    const CPubKey& GetSpendPubKey() const { return m_spend_pubkey; }
96
5
    std::span<const unsigned char> GetExtensionData() const { return m_extension_data; }
97
98
235
    bool operator==(const SilentPaymentsDestination&) const = default;
99
};
100
101
//! Decode a BIP352 "sp1..." address. Returns the destination, or an error message on failure.
102
util::Expected<SilentPaymentsDestination, std::string> DecodeSilentPaymentsAddress(
103
    const std::string& str, const CChainParams& params);
104
105
class SilentPaymentsLabel {
106
private:
107
    std::unique_ptr<secp256k1_silentpayments_label> m_label;
108
    unsigned char m_vch[CPubKey::COMPRESSED_SIZE];
109
110
    //! Parses raw bytes into a fully valid label
111
    //! returns std::nullopt if vch is not a validly-encoded label.
112
    static std::optional<SilentPaymentsLabel> FromBytes(std::span<const unsigned char, CPubKey::COMPRESSED_SIZE> vch);
113
114
public:
115
    SilentPaymentsLabel(const secp256k1_silentpayments_label& label);
116
117
    SilentPaymentsLabel(SilentPaymentsLabel&&) noexcept;
118
    SilentPaymentsLabel& operator=(SilentPaymentsLabel&&) noexcept;
119
    SilentPaymentsLabel(const SilentPaymentsLabel&);
120
    SilentPaymentsLabel& operator=(const SilentPaymentsLabel&);
121
122
    ~SilentPaymentsLabel();
123
124
1
    friend bool operator==(const SilentPaymentsLabel& a, const SilentPaymentsLabel& b) {
125
1
        return memcmp(a.m_vch,  b.m_vch, CPubKey::COMPRESSED_SIZE) == 0;
126
1
    }
127
37
    friend bool operator<(const SilentPaymentsLabel& a, const SilentPaymentsLabel& b) {
128
37
        return memcmp(a.m_vch,  b.m_vch, CPubKey::COMPRESSED_SIZE) < 0;
129
37
    }
130
0
    friend bool operator>(const SilentPaymentsLabel& a, const SilentPaymentsLabel& b) {
131
0
        return b < a;
132
0
    }
133
    //! Transparent comparisons against a raw 33-byte compressed label key, so a
134
    //! LabelTweakMap can be looked up by key bytes (e.g. from a secp256k1 callback)
135
    //! without constructing a fully-parsed SilentPaymentsLabel.
136
5.39M
    friend bool operator<(const SilentPaymentsLabel& a, std::span<const unsigned char, CPubKey::COMPRESSED_SIZE> b) {
137
5.39M
        return memcmp(a.m_vch, b.data(), CPubKey::COMPRESSED_SIZE) < 0;
138
5.39M
    }
139
532k
    friend bool operator<(std::span<const unsigned char, CPubKey::COMPRESSED_SIZE> a, const SilentPaymentsLabel& b) {
140
532k
        return memcmp(a.data(), b.m_vch, CPubKey::COMPRESSED_SIZE) < 0;
141
532k
    }
142
143
    template <typename Stream>
144
    void Serialize(Stream& s) const
145
1
    {
146
1
        s << std::span{m_vch, CPubKey::COMPRESSED_SIZE};
147
1
    }
148
149
    template <typename Stream>
150
    static std::optional<SilentPaymentsLabel> Unserialize(Stream& s)
151
1
    {
152
1
        std::array<unsigned char, CPubKey::COMPRESSED_SIZE> vch;
153
1
        s >> std::span{vch};
154
1
        return FromBytes(std::span{vch});
155
1
    }
156
157
    const secp256k1_silentpayments_label* Get() const LIFETIMEBOUND;
158
};
159
160
struct SilentPaymentsOutput {
161
    XOnlyPubKey output;
162
    uint256 tweak;
163
    std::optional<SilentPaymentsLabel> label;
164
};
165
166
/**
167
 * @brief Get the public key from an input.
168
 *
169
 * Get the public key from a silent payments eligible input. This requires knowledge of the prevout
170
 * scriptPubKey to determine the type of input and whether or not it is eligible for silent payments.
171
 *
172
 * If the input is not eligible for silent payments, the input is skipped (indicated by returning a nullopt).
173
 *
174
 * @param txin                    The transaction input.
175
 * @param spk                     The scriptPubKey of the prevout.
176
 * @return The public key, or nullopt if not found.
177
 */
178
std::optional<PubKey> GetPubKeyFromInput(const CTxIn& txin, const CScript& spk);
179
180
/**
181
 * @brief Generate silent payments taproot destinations.
182
 *
183
 * Given a set of silent payments destinations, generate the requested number of outputs. If a silent payment
184
 * destination is repeated, this indicates multiple outputs are requested for the same recipient. The silent payment
185
 * destinations are passed in a map where the key indicates their desired position in the final tx.vout array.
186
 *
187
 * @param sp_dests                            The silent payments destinations.
188
 * @param plain_keys                          The private keys for non-taproot inputs.
189
 * @param taproot_keys                        The keypairs for taproot inputs.
190
 * @param smallest_outpoint                   The smallest_outpoint from the transaction inputs.
191
 * @pre smallest_outpoint is not null, and at least one of plain_keys or taproot_keys is non-empty.
192
 * @return The generated silent payments taproot destinations or std::nullopt if the set of provided inputs is invalid:
193
 *         - The size of any group (i.e. recipients sharing the same scan public key)
194
 *           exceeds the protocol limit SECP256K1_SILENTPAYMENTS_RECIPIENT_GROUP_LIMIT.
195
 *         - There is an invalid key in the set.
196
 *         - The inputs sum to zero.
197
 */
198
std::optional<std::map<size_t, WitnessV1Taproot>> GenerateSilentPaymentsTaprootDestinations(const std::map<size_t, SilentPaymentsDestination>& sp_dests, const std::vector<CKey>& plain_keys, const std::vector<KeyPair>& taproot_keys, const COutPoint& smallest_outpoint);
199
200
enum class PrevoutsSummaryError {
201
    //! A prevout referenced by an input in `vin` has no corresponding entry in `coins`.
202
    MISSING_COIN,
203
    //! This transaction is not eligible to be scanned for silent payments outputs: either none
204
    //! of its inputs yielded a public key eligible for silent payments (e.g. an input spends an
205
    //! unknown segwit version), or the eligible inputs' public keys summed to the point at infinity.
206
    NOT_ELIGIBLE,
207
};
208
209
/**
210
 * @brief Get silent payments public data from transaction inputs.
211
 *
212
 * Get the necessary data from the transaction inputs to be able to scan the transaction outputs for silent payments outputs.
213
 * This requires knowledge of the prevout scriptPubKey, which is passed via `coins`.
214
 *
215
 * This function returns the public key sum and the input hash separately and is intended to be used by the wallet when scanning
216
 * a transaction.
217
 *
218
 * If there are no eligible inputs, or one of the inputs spends an unknown segwit version (i.e > 1), this transaction is not
219
 * eligible to be scanned for silent payments outputs; see PrevoutsSummaryError.
220
 *
221
 * @param vin                        The transaction inputs.
222
 * @param coins                      The coins (potentially) spent in this transaction.
223
 * @return util::Expected<PrevoutsSummary, PrevoutsSummaryError> The silent payments public data, or the reason it could not be computed.
224
 */
225
util::Expected<PrevoutsSummary, PrevoutsSummaryError> GetSilentPaymentsPrevoutsSummary(const std::vector<CTxIn>& vin, const std::map<COutPoint, Coin>& coins);
226
227
using LabelTweakMap = std::map<SilentPaymentsLabel, uint256, std::less<>>;
228
229
/**
230
 * @brief A silent payments recipient's scanning identity.
231
 *
232
 * Bundles a recipient's scan key, spend public key, and known labels (always including the change
233
 * label) into a single self-contained object capable of scanning transactions and deriving the
234
 * recipient's change destination.
235
 *
236
 * The change label is created automatically on construction if one is not supplied through
237
 * the labels map; additional labels are registered on creation with GenerateLabeledAddress().
238
 */
239
class SilentPaymentsReceiver {
240
private:
241
    CKey m_scan_key;
242
    CPubKey m_spend_pubkey;
243
    std::unique_ptr<secp256k1_pubkey> m_spend_pubkey_obj;
244
    LabelTweakMap m_labels;
245
    LabelTweakMap::const_iterator m_change_it;
246
247
    SilentPaymentsDestination BuildLabeledDestination(const SilentPaymentsLabel& label) const;
248
249
public:
250
    SilentPaymentsReceiver(const CKey& scan_key, const CPubKey& spend_pubkey, const LabelTweakMap& labels = {});
251
    ~SilentPaymentsReceiver();
252
253
    // Default move would leave m_scan_key and m_labels empty while
254
    // m_spend_pubkey remains fully populated, leaving the
255
    // SilentPaymentsReceiver object in an inconsistent state.
256
    SilentPaymentsReceiver(SilentPaymentsReceiver&&) = delete;
257
    SilentPaymentsReceiver& operator=(SilentPaymentsReceiver&&) = delete;
258
259
    SilentPaymentsReceiver(const SilentPaymentsReceiver&) = delete;
260
    SilentPaymentsReceiver& operator=(const SilentPaymentsReceiver&) = delete;
261
262
    /**
263
     * @brief Get this recipient's registered labels, including the change label.
264
     *
265
     * @return const LabelTweakMap& Each label mapped to its scalar tweak.
266
     */
267
    const LabelTweakMap& GetLabels() const;
268
269
    /**
270
     * @brief Register label `m` (e.g. for a labeled sub-address to hand out to a payer) and generate
271
     * the resulting address.
272
     *
273
     * Derives the label from this recipient's own scan key and `m`, registers it so Scan() can
274
     * recognize outputs sent to it, and returns the address to hand out.
275
     *
276
     * @param m                        An integer m, greater than 0 (m = 0 is reserved for the change label).
277
     * @return SilentPaymentsDestination The labeled destination, with `B_spend -> B_spend + label`.
278
     */
279
    SilentPaymentsDestination GenerateLabeledAddress(uint32_t m);
280
281
    /**
282
     * @brief Get this recipient's silent payments change destination.
283
     *
284
     * @return SilentPaymentsDestination The destination to use for this recipient's own change outputs,
285
     *         i.e. `B_spend -> B_spend + change_label`.
286
     */
287
    SilentPaymentsDestination GetChangeDestination() const;
288
289
    /**
290
     * @brief Scan a transaction for silent payments outputs.
291
     *
292
     * Scan the transaction for silent payments outputs intended for this recipient. The output, shared
293
     * secret tweak, and (optionally) label public key is returned for each output found. If the output
294
     * was sent to a labeled address, the label tweak is added to the shared secret tweak. The shared
295
     * secret tweak is needed to spend the output, by adding it to the spend secret key. If no outputs
296
     * are found, this transaction does not contain silent payments outputs for this recipient.
297
     *
298
     * @param prevouts_summary                   The silent payments public data.
299
     * @param tx_outputs                         The taproot output public keys.
300
     * @return The found outputs or std::nullopt in the case of an error.
301
     */
302
    std::optional<std::vector<SilentPaymentsOutput>> Scan(const PrevoutsSummary& prevouts_summary, const std::vector<XOnlyPubKey>& tx_outputs) const;
303
};
304
}; // namespace bip352
305
#endif // BITCOIN_COMMON_BIP352_H