Branch data Line data Source code
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 : 29 : 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 : 28 : const secp256k1_silentpayments_prevouts_summary* PrevoutsSummary::Get() const
48 : : {
49 : 28 : return m_prevouts_summary.get();
50 : : }
51 : :
52 : 135 : 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 : : ) {
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 : : }
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 : : }
67 : :
68 : 133 : util::Expected<SilentPaymentsDestination, std::string> DecodeSilentPaymentsAddress(
69 : : const std::string& str, const CChainParams& params)
70 : : {
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 : : }
78 [ + + ]: 118 : if (dec.hrp != params.SilentPaymentsHRP()) {
79 [ + - ]: 50 : return util::Unexpected{strprintf("Invalid or unsupported prefix for Silent Payments address (expected %s, got %s).", params.SilentPaymentsHRP(), dec.hrp)};
80 : : }
81 [ - + ]: 93 : if (dec.data.empty()) {
82 [ # # ]: 0 : return util::Unexpected{"Empty Bech32 data section"};
83 : : }
84 : 93 : std::vector<unsigned char> data;
85 [ + - - + ]: 6206 : 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 : : }
88 [ - + + + ]: 93 : if (data.size() < SILENT_PAYMENTS_V0_DATA_SIZE) {
89 [ + - ]: 2 : 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 : : }
91 [ + + ]: 92 : const uint8_t version = dec.data[0];
92 [ + + ]: 92 : if (version >= 31) {
93 [ + - ]: 2 : return util::Unexpected{strprintf("This implementation only supports Silent payments addresses v0 through v30 (got %d).", version)};
94 : : }
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 : : }
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 : : }
105 [ + - ]: 176 : return *sp_dest;
106 : 317 : }
107 : :
108 [ + - ]: 2376 : SilentPaymentsLabel::SilentPaymentsLabel(const secp256k1_silentpayments_label& label) {
109 [ + - ]: 2376 : m_label = std::make_unique<secp256k1_silentpayments_label>(label);
110 [ + - ]: 2376 : int ret = secp256k1_silentpayments_recipient_label_serialize(secp256k1_context_static, m_vch, m_label.get());
111 [ - + ]: 2376 : assert(ret);
112 : 2376 : }
113 : :
114 : 1 : std::optional<SilentPaymentsLabel> SilentPaymentsLabel::FromBytes(std::span<const unsigned char, CPubKey::COMPRESSED_SIZE> vch)
115 : : {
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 : : }
120 : 1 : return SilentPaymentsLabel(label_obj);
121 : : }
122 : :
123 : 8850 : SilentPaymentsLabel::SilentPaymentsLabel(SilentPaymentsLabel&&) noexcept = default;
124 [ # # ]: 0 : SilentPaymentsLabel& SilentPaymentsLabel::operator=(SilentPaymentsLabel&&) noexcept = default;
125 : 11226 : SilentPaymentsLabel::~SilentPaymentsLabel() = default;
126 : :
127 : 0 : SilentPaymentsLabel::SilentPaymentsLabel(const SilentPaymentsLabel& label)
128 : 0 : : m_label{std::make_unique<secp256k1_silentpayments_label>(*label.m_label)}
129 : : {
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 : : }
137 : 0 : return *this;
138 : : }
139 : :
140 : 15 : const secp256k1_silentpayments_label* SilentPaymentsLabel::Get() const {
141 : 15 : return m_label.get();
142 : : }
143 : :
144 : 126 : std::optional<PubKey> GetPubKeyFromInput(const CTxIn& txin, const CScript& spk)
145 : : {
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 : : }
164 [ + - ]: 4 : if (std::equal(WitnessV1Taproot::NUMS_H.begin(), WitnessV1Taproot::NUMS_H.end(), control.begin() + 1)) {
165 : 4 : return std::nullopt;
166 : : }
167 : : }
168 : :
169 [ - + ]: 18 : XOnlyPubKey key{solutions[0]};
170 [ + - - + ]: 18 : if (!key.IsFullyValid()) return std::nullopt;
171 : 18 : return PubKey{key};
172 : : }
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 : : }
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 [ - + ]: 166 : auto pc = txin.scriptSig.begin();
195 [ + + + + ]: 360 : 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 : : }
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 : : }
205 : 174 : }
206 : 6 : return std::nullopt;
207 : : }
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 : : }
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 : 6 : }
225 : :
226 : 0 : return std::nullopt;
227 : 126 : }
228 : :
229 : 30 : 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 : : {
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 [ + + ]: 77 : 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 : : }
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 [ + + ]: 39 : 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 : : }
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 : : &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 [ + - ]: 58 : return PrevoutsSummary(prevouts_summary);
272 : 30 : }
273 : :
274 : 32 : util::Expected<PrevoutsSummary, PrevoutsSummaryError> GetSilentPaymentsPrevoutsSummary(const std::vector<CTxIn>& vin, const std::map<COutPoint, Coin>& coins)
275 : : {
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 [ + + ]: 97 : 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 : : }
291 [ + - ]: 65 : tx_outpoints.emplace_back(txin.prevout);
292 [ + - ]: 65 : auto pubkey = GetPubKeyFromInput(txin, coin.out.scriptPubKey);
293 [ + + ]: 65 : if (pubkey.has_value()) {
294 [ + - ]: 122 : std::visit([&pubkeys, &xonly_pubkeys](auto&& pubkey) {
295 : : using T = std::decay_t<decltype(pubkey)>;
296 : : 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 : 9 : xonly_pubkeys.push_back(pubkey);
300 : : }
301 : : }, *pubkey);
302 : : }
303 : 66 : }
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 : 62 : }
310 : :
311 : 29 : 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 : : ) {
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 [ - + + + ]: 2391 : for (size_t i = 0; i < recipients.size(); i++) {
334 : 2362 : secp256k1_silentpayments_recipient recipient_obj;
335 [ + - ]: 2362 : ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &recipient_obj.scan_pubkey, recipients[i].GetScanPubKey().data(), recipients[i].GetScanPubKey().size());
336 [ - + ]: 2362 : assert(ret);
337 [ + - ]: 2362 : ret = secp256k1_ec_pubkey_parse(secp256k1_context_static, &recipient_obj.spend_pubkey, recipients[i].GetSpendPubKey().data(), recipients[i].GetSpendPubKey().size());
338 [ - + ]: 2362 : assert(ret);
339 : 2362 : recipient_obj.index = i;
340 [ + - ]: 2362 : recipient_objs.push_back(recipient_obj);
341 [ + - ]: 2362 : recipient_ptrs.push_back(&recipient_objs[i]);
342 : :
343 : 2362 : secp256k1_xonly_pubkey generated_output{};
344 [ + - ]: 2362 : generated_outputs.push_back(generated_output);
345 [ + - ]: 2362 : generated_output_ptrs.push_back(&generated_outputs[i]);
346 : : }
347 : :
348 [ + + ]: 74 : 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 : : }
352 [ + + ]: 38 : for (const auto& keypair : taproot_keypairs) {
353 [ - + ]: 9 : if (!keypair.IsValid()) return std::nullopt;
354 [ + - ]: 9 : taproot_keypair_ptrs.push_back(keypair.GetSecpKeypair());
355 : : }
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 [ - + + - : 58 : ret = secp256k1_silentpayments_sender_create_outputs(GetSecp256k1SignContext(),
+ - ]
362 : : generated_output_ptrs.data(),
363 : : 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 : : );
368 [ + + ]: 29 : if (!ret) return std::nullopt;
369 : 27 : return generated_outputs;
370 : 29 : }
371 : :
372 : 29 : 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 : : {
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 [ + - + + ]: 2391 : for (const auto& [_, addr] : sp_dests) {
384 [ + - ]: 2362 : recipients.push_back(addr);
385 : : }
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 [ - + - + ]: 27 : assert(sp_dests.size() == outputs->size());
391 : 27 : size_t output_i{0};
392 [ + - + + ]: 64 : 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 : : }
399 : 27 : return tr_dests;
400 : 29 : }
401 : :
402 : 5397493 : static const unsigned char* LabelLookupCallback(const unsigned char* key, const void* context) {
403 : 5397493 : auto label_context = static_cast<const LabelTweakMap*>(context);
404 : 5397493 : auto it = label_context->find(std::span<const unsigned char, CPubKey::COMPRESSED_SIZE>{key, CPubKey::COMPRESSED_SIZE});
405 [ + + ]: 5397493 : if (it != label_context->end()) {
406 : 2333 : return it->second.begin();
407 : : }
408 : : return nullptr;
409 : : }
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 [ + - ]: 84 : 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 : : }
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 : : }
431 : :
432 : 29 : 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 : : {
435 [ + - ]: 58 : 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 : : }
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 : : }
459 : :
460 : 2 : SilentPaymentsDestination SilentPaymentsReceiver::GetChangeDestination() const {
461 : 2 : return BuildLabeledDestination(m_change_it->first);
462 : : }
463 : :
464 : 28 : std::optional<std::vector<SilentPaymentsOutput>> SilentPaymentsReceiver::Scan(
465 : : const PrevoutsSummary& prevouts_summary,
466 : : const std::vector<XOnlyPubKey>& tx_outputs
467 : : ) 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 [ + + ]: 2391 : for (const XOnlyPubKey& tx_output : tx_outputs) {
482 : 2363 : secp256k1_xonly_pubkey tx_output_obj;
483 [ + - ]: 2363 : ret = secp256k1_xonly_pubkey_parse(secp256k1_context_static, &tx_output_obj, tx_output.data());
484 [ + + ]: 2363 : if (!ret) {
485 : : // It is possible that a P2TR output encodes an invalid x-only pubkey.
486 : 1 : continue;
487 : : }
488 [ + - ]: 2362 : tx_output_objs.push_back(tx_output_obj);
489 [ + - ]: 2362 : tx_output_ptrs.push_back(&tx_output_objs.back());
490 [ + - ]: 2362 : found_output_objs.emplace_back();
491 [ + - ]: 2363 : found_output_ptrs.push_back(&found_output_objs.back());
492 : : }
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 [ + - - + ]: 56 : ret = secp256k1_silentpayments_recipient_scan_outputs(secp256k1_context_static,
498 : : found_output_ptrs.data(), &n_found_outputs,
499 [ + - ]: 28 : tx_output_ptrs.data(), tx_output_ptrs.size(),
500 : : UCharCast(m_scan_key.begin()),
501 : : prevouts_summary.Get(),
502 : 28 : m_spend_pubkey_obj.get(),
503 : : LabelLookupCallback,
504 [ + - ]: 28 : &m_labels
505 : : );
506 [ - + ]: 28 : if (!ret) return std::nullopt;
507 : :
508 : 28 : std::vector<SilentPaymentsOutput> outputs;
509 [ + + ]: 2384 : for (size_t i = 0; i < n_found_outputs; i++) {
510 : 2356 : SilentPaymentsOutput sp_output;
511 [ + - ]: 2356 : ret = secp256k1_xonly_pubkey_serialize(secp256k1_context_static, sp_output.output.begin(), &found_output_objs[i].output);
512 [ - + ]: 2356 : assert(ret);
513 : 2356 : sp_output.tweak = uint256{found_output_objs[i].tweak};
514 [ + + ]: 2356 : if (found_output_objs[i].found_with_label) {
515 [ + - ]: 2333 : sp_output.label = SilentPaymentsLabel(found_output_objs[i].label);
516 : : }
517 [ + - ]: 2356 : outputs.emplace_back(std::move(sp_output));
518 : 2356 : }
519 : 28 : return outputs;
520 : 56 : }
521 : : }; // namespace bip352
|