/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 |