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