Coverage Report

Created: 2026-10-03 10:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/common/bip352.cpp
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
#include <common/bip352.h>
6
7
#include <addresstype.h>
8
#include <bech32.h>
9
#include <chainparams.h>
10
#include <coins.h>
11
#include <key.h>
12
#include <primitives/transaction.h>
13
#include <pubkey.h>
14
#include <script/interpreter.h>
15
#include <script/script.h>
16
#include <script/sign.h>
17
#include <script/solver.h>
18
#include <script/verify_flags.h>
19
#include <secp256k1.h>
20
#include <secp256k1_extrakeys.h>
21
#include <secp256k1_silentpayments.h>
22
#include <span.h>
23
#include <streams.h>
24
#include <tinyformat.h>
25
#include <uint256.h>
26
#include <util/strencodings.h>
27
28
#include <algorithm>
29
#include <cassert>
30
#include <cstddef>
31
#include <optional>
32
#include <span>
33
#include <type_traits>
34
#include <utility>
35
36
namespace bip352 {
37
38
PrevoutsSummary::PrevoutsSummary(const secp256k1_silentpayments_prevouts_summary& prevouts_summary)
39
29
  : m_prevouts_summary{std::make_unique<secp256k1_silentpayments_prevouts_summary>(prevouts_summary)} {}
40
41
87
PrevoutsSummary::PrevoutsSummary(PrevoutsSummary&&) noexcept = default;
42
0
PrevoutsSummary& PrevoutsSummary::operator=(PrevoutsSummary&&) noexcept = default;
43
44
116
PrevoutsSummary::~PrevoutsSummary() = default;
45
46
const secp256k1_silentpayments_prevouts_summary* PrevoutsSummary::Get() const
47
28
{
48
28
    return m_prevouts_summary.get();
49
28
}
50
51
std::optional<SilentPaymentsDestination> SilentPaymentsDestination::From(
52
    const CPubKey& scan_pubkey,
53
    const CPubKey& spend_pubkey,
54
    uint8_t version,
55
    std::span<const unsigned char> extension_data
56
135
) {
57
135
    if (version >= 31) return std::nullopt;
58
135
    if (version == 0 && !extension_data.empty()) {
59
        // V0 address has no extension data
60
0
        return std::nullopt;
61
0
    }
62
135
    if (!scan_pubkey.IsFullyValid() || !scan_pubkey.IsCompressed()) return std::nullopt;
63
133
    if (!spend_pubkey.IsFullyValid() || !spend_pubkey.IsCompressed()) return std::nullopt;
64
132
    return SilentPaymentsDestination(version, scan_pubkey, spend_pubkey, extension_data);
65
133
}
66
67
util::Expected<SilentPaymentsDestination, std::string> DecodeSilentPaymentsAddress(
68
    const std::string& str, const CChainParams& params)
69
133
{
70
133
    static constexpr size_t SILENT_PAYMENTS_V0_DATA_SIZE = 66;
71
133
    static constexpr size_t SP_PUBKEYS_SIZE = 2 * CPubKey::COMPRESSED_SIZE;
72
73
133
    const auto dec = bech32::Decode(str, bech32::CharLimit::SILENT_PAYMENTS);
74
133
    if (dec.encoding != bech32::Encoding::BECH32M) {
75
15
        return util::Unexpected{"Silent Payments address must use Bech32m checksum"};
76
15
    }
77
118
    if (dec.hrp != params.SilentPaymentsHRP()) {
78
25
        return util::Unexpected{strprintf("Invalid or unsupported prefix for Silent Payments address (expected %s, got %s).", params.SilentPaymentsHRP(), dec.hrp)};
79
25
    }
80
93
    if (dec.data.empty()) {
81
0
        return util::Unexpected{"Empty Bech32 data section"};
82
0
    }
83
93
    std::vector<unsigned char> data;
84
6.11k
    if (!ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin() + 1, dec.data.end())) {
85
0
        return util::Unexpected{"Invalid padding in Silent payments address (Bech32m data section)"};
86
0
    }
87
93
    if (data.size() < SILENT_PAYMENTS_V0_DATA_SIZE) {
88
1
        return util::Unexpected{strprintf("Silent payments data payload is too small (expected at least %d, got %d).", SILENT_PAYMENTS_V0_DATA_SIZE, data.size())};
89
1
    }
90
92
    const uint8_t version = dec.data[0];
91
92
    if (version >= 31) {
92
1
        return util::Unexpected{strprintf("This implementation only supports Silent payments addresses v0 through v30 (got %d).", version)};
93
1
    }
94
91
    if (version == 0 && data.size() != SILENT_PAYMENTS_V0_DATA_SIZE) {
95
0
        return util::Unexpected{strprintf("Silent payments version is v0 but data is not the correct size (expected %d, got %d).", SILENT_PAYMENTS_V0_DATA_SIZE, data.size())};
96
0
    }
97
91
    CPubKey scan_pubkey{data.begin(), data.begin() + CPubKey::COMPRESSED_SIZE};
98
91
    CPubKey spend_pubkey{data.begin() + CPubKey::COMPRESSED_SIZE, data.begin() + 2 * CPubKey::COMPRESSED_SIZE};
99
91
    std::span<unsigned char> extension_data{data.data() + SP_PUBKEYS_SIZE, data.size() - SP_PUBKEYS_SIZE};
100
91
    auto sp_dest = SilentPaymentsDestination::From(scan_pubkey, spend_pubkey, version, extension_data);
101
91
    if (!sp_dest) {
102
3
        return util::Unexpected{"Invalid Silent payments address"};
103
3
    }
104
88
    return *sp_dest;
105
91
}
106
107
2.37k
SilentPaymentsLabel::SilentPaymentsLabel(const secp256k1_silentpayments_label& label) {
108
2.37k
    m_label = std::make_unique<secp256k1_silentpayments_label>(label);
109
2.37k
    int ret = secp256k1_silentpayments_recipient_label_serialize(secp256k1_context_static, m_vch, m_label.get());
110
2.37k
    assert(ret);
111
2.37k
}
112
113
std::optional<SilentPaymentsLabel> SilentPaymentsLabel::FromBytes(std::span<const unsigned char, CPubKey::COMPRESSED_SIZE> vch)
114
1
{
115
1
    secp256k1_silentpayments_label label_obj;
116
1
    if (!secp256k1_silentpayments_recipient_label_parse(secp256k1_context_static, &label_obj, vch.data())) {
117
0
        return std::nullopt;
118
0
    }
119
1
    return SilentPaymentsLabel(label_obj);
120
1
}
121
122
8.85k
SilentPaymentsLabel::SilentPaymentsLabel(SilentPaymentsLabel&&) noexcept = default;
123
0
SilentPaymentsLabel& SilentPaymentsLabel::operator=(SilentPaymentsLabel&&) noexcept = default;
124
11.2k
SilentPaymentsLabel::~SilentPaymentsLabel() = default;
125
126
SilentPaymentsLabel::SilentPaymentsLabel(const SilentPaymentsLabel& label)
127
0
    : m_label{std::make_unique<secp256k1_silentpayments_label>(*label.m_label)}
128
0
{
129
0
    memcpy(m_vch, label.m_vch, CPubKey::COMPRESSED_SIZE);
130
0
}
131
0
SilentPaymentsLabel& SilentPaymentsLabel::operator=(const SilentPaymentsLabel& label) {
132
0
    if (this != &label) {
133
0
        m_label = std::make_unique<secp256k1_silentpayments_label>(*label.m_label);
134
0
        memcpy(m_vch, label.m_vch, CPubKey::COMPRESSED_SIZE);
135
0
    }
136
0
    return *this;
137
0
}
138
139
15
const secp256k1_silentpayments_label* SilentPaymentsLabel::Get() const {
140
15
    return m_label.get();
141
15
}
142
143
std::optional<PubKey> GetPubKeyFromInput(const CTxIn& txin, const CScript& spk)
144
125
{
145
125
    std::vector<std::vector<unsigned char>> solutions;
146
125
    const TxoutType type = Solver(spk, solutions);
147
148
125
    if (type == TxoutType::WITNESS_V1_TAPROOT) {
149
22
        const auto& stack = txin.scriptWitness.stack;
150
22
        if (stack.empty()) return std::nullopt;
151
22
        const bool has_annex = !stack.back().empty() && stack.back()[0] == ANNEX_TAG;
152
22
        const size_t effective_size = stack.size() - (has_annex ? 1 : 0);
153
154
22
        if (effective_size > 1) {
155
            // BIP-352: skip script-path spends using NUMS-H internal key.
156
            // Validate control block size before checking internal key.
157
4
            const auto& control = stack[effective_size - 1];
158
4
            if (control.size() < TAPROOT_CONTROL_BASE_SIZE ||
159
4
                control.size() > TAPROOT_CONTROL_MAX_SIZE ||
160
4
                (control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE != 0) {
161
0
                return std::nullopt;
162
0
            }
163
4
            if (std::equal(WitnessV1Taproot::NUMS_H.begin(), WitnessV1Taproot::NUMS_H.end(), control.begin() + 1)) {
164
4
                return std::nullopt;
165
4
            }
166
4
        }
167
168
18
        XOnlyPubKey key{solutions[0]};
169
18
        if (!key.IsFullyValid()) return std::nullopt;
170
18
        return PubKey{key};
171
18
    }
172
173
103
    if (type == TxoutType::WITNESS_V0_KEYHASH) {
174
15
        const auto& stack = txin.scriptWitness.stack;
175
15
        if (stack.empty()) return std::nullopt;
176
15
        CPubKey key{stack.back()};
177
15
        if (!key.IsCompressed() || !key.IsFullyValid()) return std::nullopt;
178
13
        return PubKey{key};
179
15
    }
180
181
88
    if (type == TxoutType::PUBKEYHASH) {
182
82
        std::vector<std::vector<unsigned char>> stack;
183
82
        if (!EvalScript(stack, txin.scriptSig, SCRIPT_VERIFY_NONE, DUMMY_CHECKER, SigVersion::BASE)) {
184
0
            return std::nullopt;
185
0
        }
186
82
        if (stack.empty()) return std::nullopt;
187
82
        CPubKey key{stack.back()};
188
82
        if (!key.IsCompressed() || !key.IsFullyValid()) return std::nullopt;
189
76
        return PubKey{key};
190
82
    }
191
192
6
    if (type == TxoutType::SCRIPTHASH) {
193
        // P2SH-P2WPKH only: eval scriptSig, verify redeem script is P2WPKH.
194
6
        std::vector<std::vector<unsigned char>> stack;
195
6
        if (!EvalScript(stack, txin.scriptSig, SCRIPT_VERIFY_NONE, DUMMY_CHECKER, SigVersion::BASE)) {
196
0
            return std::nullopt;
197
0
        }
198
6
        if (stack.empty()) return std::nullopt;
199
6
        CScript redeem{stack.back().begin(), stack.back().end()};
200
6
        if (Solver(redeem, solutions) != TxoutType::WITNESS_V0_KEYHASH) return std::nullopt;
201
4
        if (txin.scriptWitness.stack.empty()) return std::nullopt;
202
4
        CPubKey key{txin.scriptWitness.stack.back()};
203
4
        if (!key.IsCompressed() || !key.IsFullyValid()) return std::nullopt;
204
2
        return PubKey{key};
205
4
    }
206
207
0
    return std::nullopt;
208
6
}
209
210
static std::optional<PrevoutsSummary> CreateInputPubkeysTweak(
211
    const std::vector<CPubKey>& plain_pubkeys,
212
    const std::vector<XOnlyPubKey>& taproot_pubkeys,
213
    const COutPoint& smallest_outpoint)
214
30
{
215
30
    secp256k1_silentpayments_prevouts_summary prevouts_summary;
216
30
    std::vector<secp256k1_pubkey> plain_pubkey_objs;
217
30
    std::vector<secp256k1_pubkey*> plain_pubkey_ptrs;
218
30
    plain_pubkey_objs.reserve(plain_pubkeys.size());
219
30
    plain_pubkey_ptrs.reserve(plain_pubkeys.size());
220
47
    for (const CPubKey& pubkey : plain_pubkeys) {
221
47
        bool ret = secp256k1_ec_pubkey_parse(secp256k1_context_static,
222
47
            &plain_pubkey_objs.emplace_back(), pubkey.data(), pubkey.size());
223
        // This pubkey is expected to be valid because GetPubKeyFromInput()
224
        // already called IsFullyValid() before including it here
225
47
        assert(ret);
226
47
        plain_pubkey_ptrs.push_back(&plain_pubkey_objs.back());
227
47
    }
228
229
30
    std::vector<secp256k1_xonly_pubkey> taproot_pubkey_objs;
230
30
    std::vector<secp256k1_xonly_pubkey*> taproot_pubkey_ptrs;
231
30
    taproot_pubkey_objs.reserve(taproot_pubkeys.size());
232
30
    taproot_pubkey_ptrs.reserve(taproot_pubkeys.size());
233
30
    for (const XOnlyPubKey& pubkey : taproot_pubkeys) {
234
9
        bool ret = secp256k1_xonly_pubkey_parse(secp256k1_context_static,
235
9
            &taproot_pubkey_objs.emplace_back(), pubkey.data());
236
        // This xonlypubkey is expected to be valid because
237
        // GetPubKeyFromInput() already called IsFullyValid()
238
        // before including it here
239
9
        assert(ret);
240
9
        taproot_pubkey_ptrs.push_back(&taproot_pubkey_objs.back());
241
9
    }
242
243
30
    std::array<std::byte, 36> smallest_outpoint_ser;
244
30
    SpanWriter{smallest_outpoint_ser} << smallest_outpoint;
245
30
    bool ret = secp256k1_silentpayments_recipient_prevouts_summary_create(secp256k1_context_static,
246
30
        &prevouts_summary,
247
30
        UCharCast(smallest_outpoint_ser.data()),
248
30
        taproot_pubkey_ptrs.data(), taproot_pubkey_ptrs.size(),
249
30
        plain_pubkey_ptrs.data(), plain_pubkey_ptrs.size()
250
30
    );
251
30
    if (!ret) return std::nullopt;
252
29
    return PrevoutsSummary(prevouts_summary);
253
30
}
254
255
util::Expected<PrevoutsSummary, PrevoutsSummaryError> GetSilentPaymentsPrevoutsSummary(const std::vector<CTxIn>& vin, const std::map<COutPoint, Coin>& coins)
256
32
{
257
    // Extract the keys from the inputs
258
    // or skip if no valid inputs
259
32
    std::vector<CPubKey> pubkeys;
260
32
    std::vector<XOnlyPubKey> xonly_pubkeys;
261
32
    std::vector<COutPoint> tx_outpoints;
262
66
    for (const CTxIn& txin : vin) {
263
66
        const auto coin_it = coins.find(txin.prevout);
264
66
        if (coin_it == coins.end()) return util::Unexpected(PrevoutsSummaryError::MISSING_COIN);
265
66
        const Coin& coin = coin_it->second;
266
66
        int witness_version{0};
267
66
        std::vector<unsigned char> witness_program;
268
        // BIP352 v0 skips transactions spending future witness versions.
269
66
        if (coin.out.scriptPubKey.IsWitnessProgram(witness_version, witness_program) && witness_version > 1) {
270
1
            return util::Unexpected(PrevoutsSummaryError::NOT_ELIGIBLE);
271
1
        }
272
65
        tx_outpoints.emplace_back(txin.prevout);
273
65
        auto pubkey = GetPubKeyFromInput(txin, coin.out.scriptPubKey);
274
65
        if (pubkey.has_value()) {
275
57
            std::visit([&pubkeys, &xonly_pubkeys](auto&& pubkey) {
276
57
                using T = std::decay_t<decltype(pubkey)>;
277
57
                if constexpr (std::is_same_v<T, CPubKey>) {
278
48
                    pubkeys.push_back(pubkey);
279
48
                } else if constexpr (std::is_same_v<T, XOnlyPubKey>) {
280
9
                    xonly_pubkeys.push_back(pubkey);
281
9
                }
282
57
            }, *pubkey);
bip352.cpp:_ZZN6bip35232GetSilentPaymentsPrevoutsSummaryERKSt6vectorI5CTxInSaIS1_EERKSt3mapI9COutPoint4CoinSt4lessIS7_ESaISt4pairIKS7_S8_EEEENK3$_0clIR7CPubKeyEEDaOT_
Line
Count
Source
275
48
            std::visit([&pubkeys, &xonly_pubkeys](auto&& pubkey) {
276
48
                using T = std::decay_t<decltype(pubkey)>;
277
48
                if constexpr (std::is_same_v<T, CPubKey>) {
278
48
                    pubkeys.push_back(pubkey);
279
                } else if constexpr (std::is_same_v<T, XOnlyPubKey>) {
280
                    xonly_pubkeys.push_back(pubkey);
281
                }
282
48
            }, *pubkey);
bip352.cpp:_ZZN6bip35232GetSilentPaymentsPrevoutsSummaryERKSt6vectorI5CTxInSaIS1_EERKSt3mapI9COutPoint4CoinSt4lessIS7_ESaISt4pairIKS7_S8_EEEENK3$_0clIR11XOnlyPubKeyEEDaOT_
Line
Count
Source
275
9
            std::visit([&pubkeys, &xonly_pubkeys](auto&& pubkey) {
276
9
                using T = std::decay_t<decltype(pubkey)>;
277
                if constexpr (std::is_same_v<T, CPubKey>) {
278
                    pubkeys.push_back(pubkey);
279
9
                } else if constexpr (std::is_same_v<T, XOnlyPubKey>) {
280
9
                    xonly_pubkeys.push_back(pubkey);
281
9
                }
282
9
            }, *pubkey);
283
57
        }
284
65
    }
285
31
    if (pubkeys.size() + xonly_pubkeys.size() == 0) return util::Unexpected(PrevoutsSummaryError::NOT_ELIGIBLE);
286
30
    auto smallest_outpoint = std::min_element(tx_outpoints.begin(), tx_outpoints.end(), BIP352Comparator());
287
30
    auto tweak = CreateInputPubkeysTweak(pubkeys, xonly_pubkeys, *smallest_outpoint);
288
30
    if (!tweak.has_value()) return util::Unexpected(PrevoutsSummaryError::NOT_ELIGIBLE);
289
29
    return std::move(*tweak);
290
30
}
291
292
static std::optional<std::vector<secp256k1_xonly_pubkey>> CreateOutputs(
293
    const std::vector<SilentPaymentsDestination>& recipients,
294
    const std::vector<CKey>& plain_keys,
295
    const std::vector<KeyPair>& taproot_keypairs,
296
    const COutPoint& smallest_outpoint
297
29
) {
298
29
    bool ret;
299
29
    std::vector<const secp256k1_keypair *> taproot_keypair_ptrs;
300
29
    std::vector<const unsigned char *> plain_key_ptrs;
301
29
    taproot_keypair_ptrs.reserve(taproot_keypairs.size());
302
29
    plain_key_ptrs.reserve(plain_keys.size());
303
304
29
    std::vector<secp256k1_silentpayments_recipient> recipient_objs;
305
29
    std::vector<const secp256k1_silentpayments_recipient *> recipient_ptrs;
306
29
    recipient_objs.reserve(recipients.size());
307
29
    recipient_ptrs.reserve(recipients.size());
308
309
29
    std::vector<secp256k1_xonly_pubkey> generated_outputs;
310
29
    std::vector<secp256k1_xonly_pubkey *> generated_output_ptrs;
311
29
    generated_outputs.reserve(recipients.size());
312
29
    generated_output_ptrs.reserve(recipients.size());
313
314
2.39k
    for (size_t i = 0; i < recipients.size(); i++) {
315
2.36k
        secp256k1_silentpayments_recipient recipient_obj;
316
2.36k
        ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &recipient_obj.scan_pubkey, recipients[i].GetScanPubKey().data(), recipients[i].GetScanPubKey().size());
317
2.36k
        assert(ret);
318
2.36k
        ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &recipient_obj.spend_pubkey, recipients[i].GetSpendPubKey().data(), recipients[i].GetSpendPubKey().size());
319
2.36k
        assert(ret);
320
2.36k
        recipient_obj.index = i;
321
2.36k
        recipient_objs.push_back(recipient_obj);
322
2.36k
        recipient_ptrs.push_back(&recipient_objs[i]);
323
324
2.36k
        secp256k1_xonly_pubkey generated_output{};
325
2.36k
        generated_outputs.push_back(generated_output);
326
2.36k
        generated_output_ptrs.push_back(&generated_outputs[i]);
327
2.36k
    }
328
329
45
    for (const auto& key : plain_keys) {
330
45
        if (!key.IsValid()) return std::nullopt;
331
45
        plain_key_ptrs.push_back(UCharCast(key.begin()));
332
45
    }
333
29
    for (const auto& keypair : taproot_keypairs) {
334
9
        if (!keypair.IsValid()) return std::nullopt;
335
9
        taproot_keypair_ptrs.push_back(keypair.GetSecpKeypair());
336
9
    }
337
338
    // Serialize the outpoint
339
29
    std::array<std::byte, 36> smallest_outpoint_ser;
340
29
    SpanWriter{smallest_outpoint_ser} << smallest_outpoint;
341
342
29
    ret = secp256k1_silentpayments_sender_create_outputs(GetSecp256k1SignContext(),
343
29
        generated_output_ptrs.data(),
344
29
        recipient_ptrs.data(), recipient_ptrs.size(),
345
29
        UCharCast(smallest_outpoint_ser.data()),
346
29
        taproot_keypair_ptrs.data(), taproot_keypair_ptrs.size(),
347
29
        plain_key_ptrs.data(), plain_key_ptrs.size()
348
29
    );
349
29
    if (!ret) return std::nullopt;
350
27
    return generated_outputs;
351
29
}
352
353
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)
354
29
{
355
29
    if (sp_dests.empty()) return std::map<size_t, WitnessV1Taproot>();
356
357
29
    assert(!smallest_outpoint.IsNull());
358
29
    assert(!plain_keys.empty() || !taproot_keys.empty());
359
360
29
    bool ret;
361
29
    std::map<size_t, WitnessV1Taproot> tr_dests;
362
29
    std::vector<SilentPaymentsDestination> recipients;
363
29
    recipients.reserve(sp_dests.size());
364
2.36k
    for (const auto& [_, addr] : sp_dests) {
365
2.36k
        recipients.push_back(addr);
366
2.36k
    }
367
29
    auto outputs = CreateOutputs(recipients, plain_keys, taproot_keys, smallest_outpoint);
368
    // This will fail if any input pubkey is null or
369
    // inputs were maliciously crafted to sum to zero
370
29
    if (!outputs) return std::nullopt;
371
29
    assert(sp_dests.size() == outputs->size());
372
27
    size_t output_i{0};
373
37
    for (const auto& [i, _] : sp_dests) {
374
37
        unsigned char xonly_pubkey_bytes[32];
375
37
        ret = secp256k1_xonly_pubkey_serialize(secp256k1_context_static, xonly_pubkey_bytes, &outputs.value()[output_i]);
376
37
        assert(ret);
377
37
        tr_dests[i] = WitnessV1Taproot{XOnlyPubKey{xonly_pubkey_bytes}};
378
37
        output_i++;
379
37
    }
380
27
    return tr_dests;
381
27
}
382
383
5.39M
static const unsigned char* LabelLookupCallback(const unsigned char* key, const void* context) {
384
5.39M
    auto label_context = static_cast<const LabelTweakMap*>(context);
385
5.39M
    auto it = label_context->find(std::span<const unsigned char, CPubKey::COMPRESSED_SIZE>{key, CPubKey::COMPRESSED_SIZE});
386
5.39M
    if (it != label_context->end()) {
387
2.33k
        return it->second.begin();
388
2.33k
    }
389
5.39M
    return nullptr;
390
5.39M
}
391
392
42
static std::pair<SilentPaymentsLabel, uint256> CreateLabel(const CKey& scan_key, const uint32_t m) {
393
42
    secp256k1_silentpayments_label label_obj;
394
42
    unsigned char label_tweak[32];
395
42
    bool ret = secp256k1_silentpayments_recipient_label_create(GetSecp256k1SignContext(), &label_obj, label_tweak, UCharCast(scan_key.data()), m);
396
42
    assert(ret);
397
42
    return {SilentPaymentsLabel(label_obj), uint256{label_tweak}};
398
42
}
399
400
15
static CPubKey CreateLabeledSpendPubKey(const CPubKey& spend_pubkey, const SilentPaymentsLabel& label) {
401
15
    secp256k1_pubkey spend_obj, labeled_spend_obj;
402
15
    bool ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &spend_obj, spend_pubkey.data(), spend_pubkey.size());
403
15
    assert(ret);
404
15
    ret = secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(secp256k1_context_static, &labeled_spend_obj, &spend_obj, label.Get());
405
15
    assert(ret);
406
15
    size_t pubkeylen = CPubKey::COMPRESSED_SIZE;
407
15
    CPubKey labeled_spend_pubkey;
408
15
    ret = secp256k1_ec_pubkey_serialize(secp256k1_context_static, (unsigned char*)labeled_spend_pubkey.begin(), &pubkeylen, &labeled_spend_obj, SECP256K1_EC_COMPRESSED);
409
15
    assert(ret);
410
15
    return labeled_spend_pubkey;
411
15
}
412
413
SilentPaymentsReceiver::SilentPaymentsReceiver(const CKey& scan_key, const CPubKey& spend_pubkey,
414
29
    const LabelTweakMap& labels) : m_scan_key(scan_key), m_spend_pubkey(spend_pubkey), m_labels(labels)
415
29
{
416
29
    m_change_it = m_labels.emplace(CreateLabel(scan_key, 0)).first;
417
29
    m_spend_pubkey_obj = std::make_unique<secp256k1_pubkey>();
418
29
    int ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, m_spend_pubkey_obj.get(), m_spend_pubkey.data(), m_spend_pubkey.size());
419
29
    assert(ret);
420
29
}
421
422
29
SilentPaymentsReceiver::~SilentPaymentsReceiver() = default;
423
424
2
const LabelTweakMap& SilentPaymentsReceiver::GetLabels() const {
425
2
    return m_labels;
426
2
}
427
428
15
SilentPaymentsDestination SilentPaymentsReceiver::BuildLabeledDestination(const SilentPaymentsLabel& label) const {
429
15
    CPubKey labeled_spend_pubkey = CreateLabeledSpendPubKey(m_spend_pubkey, label);
430
15
    auto dest{SilentPaymentsDestination::From(m_scan_key.GetPubKey(), labeled_spend_pubkey)};
431
15
    assert(dest);
432
15
    return *dest;
433
15
}
434
435
13
SilentPaymentsDestination SilentPaymentsReceiver::GenerateLabeledAddress(uint32_t m) {
436
13
    assert(m >= 1);
437
13
    auto it = m_labels.emplace(CreateLabel(m_scan_key, m)).first;
438
13
    return BuildLabeledDestination(it->first);
439
13
}
440
441
2
SilentPaymentsDestination SilentPaymentsReceiver::GetChangeDestination() const {
442
2
    return BuildLabeledDestination(m_change_it->first);
443
2
}
444
445
std::optional<std::vector<SilentPaymentsOutput>> SilentPaymentsReceiver::Scan(
446
    const PrevoutsSummary& prevouts_summary,
447
    const std::vector<XOnlyPubKey>& tx_outputs
448
28
) const {
449
28
    bool ret;
450
28
    std::vector<secp256k1_silentpayments_found_output> found_output_objs;
451
28
    std::vector<secp256k1_silentpayments_found_output *> found_output_ptrs;
452
28
    std::vector<secp256k1_xonly_pubkey> tx_output_objs;
453
28
    std::vector<const secp256k1_xonly_pubkey *> tx_output_ptrs;
454
28
    found_output_objs.reserve(tx_outputs.size());
455
28
    found_output_ptrs.reserve(tx_outputs.size());
456
28
    tx_output_objs.reserve(tx_outputs.size());
457
28
    tx_output_ptrs.reserve(tx_outputs.size());
458
459
28
    assert(m_scan_key.IsValid());
460
28
    assert(m_spend_pubkey_obj);
461
462
2.36k
    for (const XOnlyPubKey& tx_output : tx_outputs) {
463
2.36k
        secp256k1_xonly_pubkey tx_output_obj;
464
2.36k
        ret = secp256k1_xonly_pubkey_parse(secp256k1_context_static, &tx_output_obj, tx_output.data());
465
2.36k
        if (!ret) {
466
            // It is possible that a P2TR output encodes an invalid x-only pubkey.
467
1
            continue;
468
1
        }
469
2.36k
        tx_output_objs.push_back(tx_output_obj);
470
2.36k
        tx_output_ptrs.push_back(&tx_output_objs.back());
471
2.36k
        found_output_objs.emplace_back();
472
2.36k
        found_output_ptrs.push_back(&found_output_objs.back());
473
2.36k
    }
474
28
    if (tx_output_ptrs.empty()) return std::vector<SilentPaymentsOutput>{};
475
476
    // Scan the outputs!
477
28
    uint32_t n_found_outputs = 0;
478
28
    ret = secp256k1_silentpayments_recipient_scan_outputs(secp256k1_context_static,
479
28
        found_output_ptrs.data(), &n_found_outputs,
480
28
        tx_output_ptrs.data(), tx_output_ptrs.size(),
481
28
        UCharCast(m_scan_key.begin()),
482
28
        prevouts_summary.Get(),
483
28
        m_spend_pubkey_obj.get(),
484
28
        LabelLookupCallback,
485
28
        &m_labels
486
28
    );
487
28
    if (!ret) return std::nullopt;
488
489
28
    std::vector<SilentPaymentsOutput> outputs;
490
2.38k
    for (size_t i = 0; i < n_found_outputs; i++) {
491
2.35k
        SilentPaymentsOutput sp_output;
492
2.35k
        ret = secp256k1_xonly_pubkey_serialize(secp256k1_context_static, sp_output.output.begin(), &found_output_objs[i].output);
493
2.35k
        assert(ret);
494
2.35k
        sp_output.tweak = uint256{found_output_objs[i].tweak};
495
2.35k
        if (found_output_objs[i].found_with_label) {
496
2.33k
            sp_output.label = SilentPaymentsLabel(found_output_objs[i].label);
497
2.33k
        }
498
2.35k
        outputs.emplace_back(std::move(sp_output));
499
2.35k
    }
500
28
    return outputs;
501
28
}
502
}; // namespace bip352