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