Branch data Line data Source code
1 : : // Copyright (c) 2026-present 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 : : #include <chainparams.h>
7 : : #include <coins.h>
8 : : #include <span.h>
9 : : #include <addresstype.h>
10 : : #include <policy/policy.h>
11 : : #include <script/interpreter.h>
12 : : #include <script/script.h>
13 : : #include <script/script_error.h>
14 : : #include <script/solver.h>
15 : : #include <test/data/bip352_send_and_receive_vectors.json.h>
16 : :
17 : : #include <test/util/setup_common.h>
18 : :
19 : : #include <boost/test/unit_test.hpp>
20 : : #include <test/util/json.h>
21 : : #include <vector>
22 : : #include <util/chaintype.h>
23 : : #include <util/strencodings.h>
24 : : #include <streams.h>
25 : :
26 : : namespace bip352 {
27 : : BOOST_FIXTURE_TEST_SUITE(bip352_tests, BasicTestingSetup)
28 : :
29 : 117 : CKey ParseHexToCKey(std::string_view hex) {
30 : 117 : CKey output;
31 [ + - ]: 117 : std::vector<unsigned char> hex_data = ParseHex(hex);
32 [ + - ]: 117 : output.Set(hex_data.begin(), hex_data.end(), true);
33 : 117 : return output;
34 : 117 : };
35 : :
36 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(bip352_send_and_receive_test_vectors)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
37 : : {
38 [ + - ]: 1 : UniValue tests;
39 [ + - + - : 1 : BOOST_REQUIRE(tests.read(json_tests::bip352_send_and_receive_vectors));
+ - ]
40 : :
41 [ + - + + ]: 29 : for (const auto& vec : tests.getValues()) {
42 : : // run sending tests
43 [ + - + - : 28 : BOOST_TEST_MESSAGE(vec["comment"].get_str());
+ - + - +
- + - ]
44 : :
45 [ + - + - : 56 : for (const auto& sender : vec["sending"].getValues()) {
+ - + + ]
46 [ + - + - ]: 28 : const UniValue& given = sender["given"];
47 [ + - + - ]: 28 : const UniValue& expected = sender["expected"];
48 : :
49 : 28 : std::vector<COutPoint> outpoints;
50 : 28 : std::vector<CKey> keys;
51 : 28 : std::vector<KeyPair> taproot_keys;
52 [ + - + - : 88 : for (const auto& input : given["vin"].getValues()) {
+ - + + ]
53 [ + - + - : 120 : COutPoint outpoint{Txid::FromHex(input["txid"].get_str()).value(), input["vout"].getInt<uint32_t>()};
+ - - + +
- + - + -
+ - ]
54 [ + - ]: 60 : outpoints.push_back(outpoint);
55 [ + - + - : 60 : const auto& spk_bytes = ParseHex(input["prevout"]["scriptPubKey"]["hex"].get_str());
+ - + - +
- + - + -
- + + - ]
56 : 60 : CScript spk = CScript(spk_bytes.begin(), spk_bytes.end());
57 [ + - + - : 60 : const auto& script_sig_bytes = ParseHex(input["scriptSig"].get_str());
+ - - + +
- ]
58 : 60 : CScript script_sig = CScript(script_sig_bytes.begin(), script_sig_bytes.end());
59 [ + - ]: 60 : CTxIn txin{outpoint, script_sig};
60 : : // read the field txWitness as a stream and write txWitness >> witness.stack;
61 [ + - + - : 60 : const auto witness_str = ParseHex(input["txinwitness"].get_str());
+ - - + +
- ]
62 [ + + ]: 60 : if (!witness_str.empty()) {
63 [ - + + - ]: 19 : SpanReader(witness_str) >> txin.scriptWitness.stack;
64 : : }
65 : :
66 : : // check if this is a silent payments input by trying to extract the public key
67 [ + - ]: 60 : const auto& pubkey = GetPubKeyFromInput(txin, spk);
68 [ + + ]: 60 : if (pubkey.has_value()) {
69 : 52 : std::vector<std::vector<unsigned char>> solutions;
70 [ + - ]: 52 : TxoutType type = Solver(spk, solutions);
71 [ + + ]: 52 : if (type == TxoutType::WITNESS_V1_TAPROOT) {
72 [ + - + - : 18 : taproot_keys.emplace_back(ParseHexToCKey(input["private_key"].get_str()).ComputeKeyPair(nullptr));
+ - - + +
- + - +
- ]
73 : : } else {
74 [ + - + - : 86 : keys.emplace_back(ParseHexToCKey(input["private_key"].get_str()));
+ - - + +
- + - ]
75 : : }
76 : 52 : }
77 : 120 : }
78 [ + + + + ]: 28 : if (taproot_keys.empty() && keys.empty()) {
79 [ + - + - : 2 : BOOST_CHECK(expected["outputs"].getValues()[0].empty());
+ - + - -
+ + - ]
80 : 1 : continue;
81 : : }
82 : : // silent payments logic
83 : 27 : auto smallest_outpoint = std::min_element(outpoints.begin(), outpoints.end(), BIP352Comparator());
84 [ + - ]: 27 : std::map<size_t, SilentPaymentsDestination> sp_dests;
85 [ + - + - : 27 : const std::vector<UniValue>& silent_payments_addresses = given["recipients"].getValues();
+ - ]
86 : 27 : size_t sp_index = 0;
87 [ - + + + ]: 63 : for (size_t i = 0; i < silent_payments_addresses.size(); ++i) {
88 [ + - + - : 36 : auto sp = DecodeSilentPaymentsAddress(silent_payments_addresses[i]["address"].get_str(), Params());
+ - + - +
- ]
89 [ + - + - : 72 : BOOST_REQUIRE(sp.has_value());
+ - ]
90 [ + - + - : 36 : if (!silent_payments_addresses[i]["scan_pub_key"].isNull()) {
+ - ]
91 [ + - + - : 36 : BOOST_CHECK_EQUAL(HexStr(sp->GetScanPubKey()), silent_payments_addresses[i]["scan_pub_key"].get_str());
+ - + - +
- + - ]
92 [ + - + - : 36 : BOOST_CHECK_EQUAL(HexStr(sp->GetSpendPubKey()), silent_payments_addresses[i]["spend_pub_key"].get_str());
+ - + - +
- + - ]
93 : : }
94 [ + - + - : 36 : size_t count = silent_payments_addresses[i]["count"].isNull() ? 1 : (size_t)silent_payments_addresses[i]["count"].getInt<int>();
+ + + - +
- + - -
- ]
95 [ + + ]: 2395 : for (size_t j = 0; j < count; ++j) {
96 [ + - ]: 2359 : sp_dests.emplace(sp_index++, *sp);
97 : : }
98 : 36 : }
99 [ + - ]: 27 : auto sp_tr_dests = GenerateSilentPaymentsTaprootDestinations(sp_dests, keys, taproot_keys, *smallest_outpoint);
100 : : // This means the inputs summed to zero, which realistically would only happen maliciously. In this case, just move on
101 [ + + ]: 27 : if (!sp_tr_dests.has_value()) {
102 : : // Check that we actually expect zero outputs to be generated for this test
103 [ + - + - : 4 : BOOST_CHECK(expected["outputs"].getValues()[0].empty());
+ - + - -
+ + - ]
104 : 2 : continue;
105 : : }
106 : 25 : bool match = false;
107 [ + - + - : 30 : for (const auto& candidate_set : expected["outputs"].getValues()) {
+ - + - ]
108 [ + - - + : 60 : BOOST_CHECK(sp_tr_dests->size() == candidate_set.size());
+ - + - ]
109 : 30 : std::vector<WitnessV1Taproot> expected_spks;
110 [ + - + + ]: 82 : for (const auto& output : candidate_set.getValues()) {
111 [ + - - + : 104 : const WitnessV1Taproot tap{XOnlyPubKey(ParseHex(output.get_str()))};
+ - ]
112 [ + - ]: 52 : expected_spks.push_back(tap);
113 : : }
114 : 69 : match = std::all_of(sp_tr_dests->begin(), sp_tr_dests->end(), [&](const auto& entry) {
115 : 39 : return std::find(expected_spks.begin(), expected_spks.end(), entry.second) != expected_spks.end();
116 : : });
117 [ + + ]: 30 : if (match) break;
118 : 30 : }
119 [ + - + - ]: 50 : BOOST_CHECK(match);
120 : 28 : }
121 : :
122 : : // Test receiving
123 [ + - + - : 57 : for (const auto& recipient : vec["receiving"].getValues()) {
+ - + + ]
124 : :
125 [ + - + - ]: 29 : const UniValue& given = recipient["given"];
126 [ + - + - ]: 29 : const UniValue& expected = recipient["expected"];
127 : :
128 : 29 : std::vector<CTxIn> vin;
129 [ + - ]: 29 : std::map<COutPoint, Coin> coins;
130 [ + - + - : 91 : for (const auto& input : given["vin"].getValues()) {
+ - + + ]
131 [ + - + - : 124 : COutPoint outpoint{Txid::FromHex(input["txid"].get_str()).value(), input["vout"].getInt<uint32_t>()};
+ - - + +
- + - + -
+ - ]
132 [ + - + - : 62 : const auto& spk_bytes = ParseHex(input["prevout"]["scriptPubKey"]["hex"].get_str());
+ - + - +
- + - + -
- + + - ]
133 : 62 : CScript spk = CScript(spk_bytes.begin(), spk_bytes.end());
134 [ + - + - : 62 : const auto& script_sig_bytes = ParseHex(input["scriptSig"].get_str());
+ - - + +
- ]
135 : 62 : CScript script_sig = CScript(script_sig_bytes.begin(), script_sig_bytes.end());
136 [ + - ]: 62 : CTxIn txin{outpoint, script_sig};
137 : : // read the field txWitness as a stream and write txWitness >> witness.stack;
138 [ + - + - : 62 : const auto witness_str = ParseHex(input["txinwitness"].get_str());
+ - - + +
- ]
139 [ + + ]: 62 : if (!witness_str.empty()) {
140 [ - + + - ]: 19 : SpanReader(witness_str) >> txin.scriptWitness.stack;
141 : : }
142 [ + - ]: 62 : vin.push_back(txin);
143 [ + - + - ]: 62 : coins[outpoint] = Coin{CTxOut{{}, spk}, 0, false};
144 : 124 : }
145 [ + - ]: 29 : auto pub_tweak_data = GetSilentPaymentsPrevoutsSummary(vin, coins);
146 : : // If we don't get any tweak data from the transaction inputs, it is not a silent payment
147 : : // transaction, so we skip it.
148 [ + + ]: 29 : if (!pub_tweak_data.has_value()) {
149 : : // Make sure this is expected and not just a failure of the GetSilentPaymentsPrevoutsSummary func
150 [ + - + - : 4 : BOOST_CHECK(expected["outputs"].empty());
+ - - + +
- ]
151 : 2 : continue;
152 : : }
153 : 27 : std::vector<XOnlyPubKey> output_pub_keys;
154 [ + - + - : 2388 : for (const auto& pubkey : given["outputs"].getValues()) {
+ - + + ]
155 [ + - - + : 4722 : output_pub_keys.emplace_back(ParseHex(pubkey.get_str()));
+ - + - ]
156 : : }
157 : :
158 [ + - + - : 54 : CKey scan_priv_key = ParseHexToCKey(given["key_material"]["scan_priv_key"].get_str());
+ - + - +
- - + +
- ]
159 [ + - + - : 54 : CKey spend_priv_key = ParseHexToCKey(given["key_material"]["spend_priv_key"].get_str());
+ - + - +
- - + +
- ]
160 [ + - + - : 54 : SilentPaymentsDestination sp_address{SilentPaymentsDestination::From(scan_priv_key.GetPubKey(), spend_priv_key.GetPubKey()).value()};
+ - ]
161 [ + - + - : 27 : auto expected_address = DecodeSilentPaymentsAddress(expected["addresses"][0].get_str(), Params());
+ - + - +
- + - ]
162 [ + - + - : 54 : BOOST_REQUIRE(expected_address.has_value());
+ - ]
163 [ + - + - : 54 : BOOST_CHECK(sp_address == *expected_address);
+ - ]
164 : :
165 : : // The change label is registered automatically; only non-change labels need registering.
166 [ + - ]: 27 : SilentPaymentsReceiver receiver{scan_priv_key, sp_address.GetSpendPubKey()};
167 [ + - + - : 27 : auto given_labels{given["labels"].getValues()};
+ - + - ]
168 [ - + + + ]: 42 : for (size_t i = 0; i < given_labels.size(); i++) {
169 [ + - ]: 15 : const uint32_t m = given_labels[i].getInt<uint32_t>();
170 [ + + + - : 15 : const SilentPaymentsDestination labeled_addr = (m == 0) ? receiver.GetChangeDestination() : receiver.GenerateLabeledAddress(m);
+ - ]
171 : : // expected["addresses"] contains the base silent payments address (at index 0)
172 : : // followed by the labeled addresses
173 [ + - + - : 15 : auto sp = DecodeSilentPaymentsAddress(expected["addresses"][i+1].get_str(), Params());
+ - + - +
- + - ]
174 [ + - + - : 30 : BOOST_REQUIRE(sp.has_value());
+ - ]
175 [ + - + - ]: 30 : BOOST_CHECK(labeled_addr == *sp);
176 : 15 : }
177 : :
178 : : // Scanning
179 [ + - ]: 27 : const auto& found_outputs = receiver.Scan(*pub_tweak_data, output_pub_keys);
180 [ + - + - : 54 : BOOST_REQUIRE(found_outputs.has_value());
+ + ]
181 : : // The transaction may be a silent payments transaction, but it does not contain any outputs for us,
182 : : // so we continue to the next transaction.
183 [ + + ]: 27 : if (found_outputs->empty()) {
184 [ + - + - : 2 : BOOST_CHECK(expected["outputs"].empty());
+ - - + +
- ]
185 : 1 : continue;
186 : : }
187 [ + - + - : 26 : if (!expected["n_outputs"].isNull()) {
+ + ]
188 [ + - + - : 1 : BOOST_CHECK_EQUAL(found_outputs->size(), (size_t)expected["n_outputs"].getInt<int>());
+ - + - -
+ + - ]
189 : : } else {
190 [ + - ]: 25 : std::map<XOnlyPubKey, uint256> expected_outputs;
191 [ + - + - : 57 : for (const auto& output : expected["outputs"].getValues()) {
+ - + + ]
192 : 64 : expected_outputs.emplace(
193 [ + - + - : 96 : XOnlyPubKey{ParseHex(output["pub_key"].get_str())},
- + + - +
- ]
194 [ + - + - : 96 : uint256{ParseHex(output["priv_key_tweak"].get_str())});
+ - - + +
- + - ]
195 : : }
196 [ + - + - : 25 : BOOST_TEST_MESSAGE(found_outputs->size());
- + + - ]
197 [ + - - + : 25 : BOOST_REQUIRE_EQUAL(found_outputs->size(), expected_outputs.size());
+ - ]
198 [ + + ]: 57 : for (const auto& output : *found_outputs) {
199 : 32 : const auto expected_output = expected_outputs.find(output.output);
200 [ + - + - : 64 : BOOST_REQUIRE(expected_output != expected_outputs.end());
+ - ]
201 [ + - + - ]: 64 : BOOST_CHECK(output.tweak == expected_output->second);
202 : : }
203 : 25 : }
204 : 30 : }
205 : : }
206 : 1 : }
207 : :
208 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(bip352_preserves_requested_output_indexes)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
209 : : {
210 : 1 : CKey sender_key = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000001");
211 [ + - ]: 1 : CKey scan_key = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000002");
212 [ + - ]: 1 : CKey spend_key = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000003");
213 [ + - + - : 2 : SilentPaymentsDestination sp_dest{SilentPaymentsDestination::From(scan_key.GetPubKey(), spend_key.GetPubKey()).value()};
+ - ]
214 [ + - + + : 4 : std::map<size_t, SilentPaymentsDestination> sp_dests{{2, sp_dest}, {5, sp_dest}};
- - ]
215 [ + - + - ]: 2 : COutPoint smallest_outpoint{Txid::FromHex("0000000000000000000000000000000000000000000000000000000000000001").value(), 0};
216 : :
217 [ + - + - : 2 : auto generated = GenerateSilentPaymentsTaprootDestinations(sp_dests, {sender_key}, {}, smallest_outpoint);
+ + - - ]
218 : :
219 [ + - + - : 2 : BOOST_REQUIRE(generated.has_value());
+ - ]
220 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(generated->size(), sp_dests.size());
221 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(generated->count(0), 0);
222 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(generated->count(1), 0);
223 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(generated->count(2), 1);
224 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(generated->count(5), 1);
225 [ + - - + : 4 : }
+ - - - ]
226 : :
227 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(bip352_skips_transactions_spending_unknown_segwit_versions)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
228 : : {
229 : 1 : CKey key = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000001");
230 [ + - ]: 1 : CPubKey pubkey = key.GetPubKey();
231 [ + - + - ]: 2 : COutPoint eligible_outpoint{Txid::FromHex("0000000000000000000000000000000000000000000000000000000000000001").value(), 0};
232 [ + - + - ]: 2 : COutPoint unknown_segwit_outpoint{Txid::FromHex("0000000000000000000000000000000000000000000000000000000000000002").value(), 0};
233 : :
234 [ + - ]: 1 : CTxIn eligible_input{eligible_outpoint};
235 [ + - ]: 1 : eligible_input.scriptWitness.stack.emplace_back(64, 0);
236 [ + - ]: 1 : eligible_input.scriptWitness.stack.emplace_back(pubkey.begin(), pubkey.end());
237 : :
238 [ + - ]: 1 : std::map<COutPoint, Coin> coins;
239 [ + - + - : 1 : coins[eligible_outpoint] = Coin{CTxOut{{}, GetScriptForDestination(WitnessV0KeyHash{pubkey})}, 0, false};
+ - + - ]
240 [ + - + - : 2 : coins[unknown_segwit_outpoint] = Coin{CTxOut{{}, GetScriptForDestination(WitnessUnknown{2, std::vector<unsigned char>(32, 1)})}, 0, false};
+ - + - +
- ]
241 : :
242 [ + - + - : 3 : BOOST_REQUIRE(GetSilentPaymentsPrevoutsSummary({eligible_input}, coins).has_value());
+ - + - +
- + + -
- ]
243 [ + - + - : 6 : BOOST_CHECK(!GetSilentPaymentsPrevoutsSummary({eligible_input, CTxIn{unknown_segwit_outpoint}}, coins).has_value());
+ - + - +
- + + -
- ]
244 [ + - + - : 4 : }
+ - - - ]
245 : :
246 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(bip352_scan_skips_invalid_taproot_outputs)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
247 : : {
248 : 1 : CKey sender_key = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000001");
249 [ + - ]: 1 : CKey scan_key = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000002");
250 [ + - ]: 1 : CKey spend_key = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000003");
251 [ + - + - ]: 2 : const COutPoint outpoint{Txid::FromHex("0000000000000000000000000000000000000000000000000000000000000001").value(), 0};
252 : :
253 [ + - ]: 1 : std::map<size_t, SilentPaymentsDestination> sp_dests;
254 [ + - + - : 2 : sp_dests.emplace(0, SilentPaymentsDestination::From(scan_key.GetPubKey(), spend_key.GetPubKey()).value());
+ - + - ]
255 [ + - + - : 2 : const auto sp_tr_dests = GenerateSilentPaymentsTaprootDestinations(sp_dests, {sender_key}, {}, outpoint);
+ + - - ]
256 [ + - + - : 2 : BOOST_REQUIRE(sp_tr_dests.has_value());
+ - ]
257 [ + - ]: 1 : const XOnlyPubKey expected_output{sp_tr_dests->begin()->second};
258 : :
259 [ + - ]: 1 : CTxIn txin{outpoint};
260 [ + - ]: 1 : const CPubKey sender_pubkey{sender_key.GetPubKey()};
261 [ + - ]: 1 : txin.scriptWitness.stack.emplace_back();
262 [ + - ]: 1 : txin.scriptWitness.stack.emplace_back(sender_pubkey.begin(), sender_pubkey.end());
263 : :
264 [ + - ]: 1 : std::map<COutPoint, Coin> coins;
265 [ + - + - : 1 : coins[outpoint] = Coin{CTxOut{{}, GetScriptForDestination(WitnessV0KeyHash{sender_pubkey})}, 0, false};
+ - + - ]
266 [ + - + - : 2 : const auto prevouts_summary = GetSilentPaymentsPrevoutsSummary({txin}, coins);
+ + - - ]
267 [ + - + - : 2 : BOOST_REQUIRE(prevouts_summary.has_value());
+ - ]
268 : :
269 : 1 : std::vector<XOnlyPubKey> output_pub_keys;
270 [ + - + - ]: 1 : output_pub_keys.emplace_back(ParseHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
271 [ + - ]: 1 : output_pub_keys.push_back(expected_output);
272 : :
273 [ + - + - ]: 1 : SilentPaymentsReceiver receiver{scan_key, spend_key.GetPubKey()};
274 [ + - ]: 1 : const auto found_outputs = receiver.Scan(*prevouts_summary, output_pub_keys);
275 [ + - + - : 2 : BOOST_REQUIRE(found_outputs.has_value());
+ - ]
276 [ + - - + : 1 : BOOST_REQUIRE_EQUAL(found_outputs->size(), 1);
+ - ]
277 [ + - + - ]: 2 : BOOST_CHECK(found_outputs->front().output == expected_output);
278 [ + - + - ]: 4 : }
279 : :
280 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(bip352_p2pkh_pubkey_extraction_with_checksig_in_scriptsig)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
281 : : {
282 : : // P2PKH scriptSigs are third-party malleable. Construct one that contains an OP_CHECKSIG with a
283 : : // bogus (but strictly DER-encoded) signature, followed by a conditional push of a wrong pubkey:
284 : : //
285 : : // <sig> <pubkey> <bogus_sig> OP_OVER OP_CHECKSIG OP_IF <wrong_pubkey> OP_ENDIF
286 : : //
287 : : // In real validation the OP_CHECKSIG fails, the OP_IF branch is skipped and the final stack is
288 : : // <sig> <pubkey>, i.e. the spend is still valid and the pubkey must be extracted from it.
289 : 1 : const CKey key = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000001");
290 [ + - ]: 1 : const CPubKey pubkey = key.GetPubKey();
291 [ + - + - ]: 1 : const CPubKey wrong_pubkey = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000002").GetPubKey();
292 [ + - + - ]: 1 : const CScript spk{GetScriptForDestination(PKHash{pubkey})};
293 : :
294 [ + - ]: 1 : CMutableTransaction tx;
295 [ + - + - ]: 2 : tx.vin.emplace_back(COutPoint{Txid::FromHex("0000000000000000000000000000000000000000000000000000000000000001").value(), 0});
296 : 1 : std::vector<unsigned char> sig;
297 [ + - + - : 2 : BOOST_REQUIRE(key.Sign(SignatureHash(spk, tx, 0, SIGHASH_ALL, /*amount=*/0, SigVersion::BASE), sig));
+ - + - +
- ]
298 [ + - ]: 1 : sig.push_back(SIGHASH_ALL);
299 [ + - ]: 1 : const std::vector<unsigned char> bogus_sig{ParseHex("300602010102010101")}; // r=1, s=1, SIGHASH_ALL
300 [ - + + - : 4 : tx.vin[0].scriptSig = CScript() << sig << ToByteVector(pubkey) << bogus_sig << OP_OVER << OP_CHECKSIG
- + + - +
- ]
301 [ + - + - : 2 : << OP_IF << ToByteVector(wrong_pubkey) << OP_ENDIF;
+ - ]
302 [ + - ]: 1 : const CTxIn& txin{tx.vin[0]};
303 : :
304 : : // Verify that the malleated spend is consensus-valid
305 : 1 : ScriptError serror = SCRIPT_ERR_OK;
306 [ + - + - : 2 : BOOST_CHECK_MESSAGE(VerifyScript(txin.scriptSig, spk, nullptr, MANDATORY_SCRIPT_VERIFY_FLAGS,
+ - + - ]
307 : : MutableTransactionSignatureChecker(&tx, 0, /*amountIn=*/0, MissingDataBehavior::ASSERT_FAIL), &serror),
308 : : ScriptErrorString(serror));
309 : :
310 [ + - ]: 1 : const auto extracted_pubkey = GetPubKeyFromInput(txin, spk);
311 [ + - + - : 2 : BOOST_REQUIRE(extracted_pubkey.has_value());
+ - ]
312 [ + - - + : 2 : BOOST_CHECK(std::get<CPubKey>(*extracted_pubkey) == pubkey);
+ - ]
313 : 2 : }
314 : :
315 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(bip352_label_serialize_roundtrip)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
316 : : {
317 : 1 : CKey scan_key = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000002");
318 [ + - + - ]: 1 : CPubKey spend_pubkey = ParseHexToCKey("0000000000000000000000000000000000000000000000000000000000000003").GetPubKey();
319 [ + - ]: 1 : SilentPaymentsReceiver receiver{scan_key, spend_pubkey};
320 : :
321 [ + - + - : 1 : BOOST_REQUIRE_EQUAL(receiver.GetLabels().size(), 1);
+ - ]
322 [ + - + - ]: 1 : const SilentPaymentsLabel& label = receiver.GetLabels().begin()->first;
323 : :
324 : 1 : DataStream stream;
325 [ + - ]: 1 : label.Serialize(stream);
326 [ + - ]: 1 : auto roundtripped = SilentPaymentsLabel::Unserialize(stream);
327 [ + - + - : 2 : BOOST_REQUIRE(roundtripped.has_value());
+ - ]
328 [ + - + - : 2 : BOOST_CHECK(*roundtripped == label);
+ - ]
329 [ + - - + : 2 : BOOST_CHECK(stream.empty());
+ - ]
330 : 1 : }
331 : :
332 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(bip352_decode_address)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
333 : : {
334 : 1 : struct ValidVector {
335 : : ChainType chain;
336 : : std::string address;
337 : : std::string scan_pubkey;
338 : : std::string spend_pubkey;
339 : : std::string extension_data;
340 : : uint8_t version;
341 : : };
342 : 1 : const ValidVector valid_vectors[]{
343 : : {ChainType::MAIN,
344 : : "sp1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68gdjvfn2",
345 : : "0295fa435f2d74f73b4baaec28115df0b32e0f1b10a340ade948c56a647fe92cdc",
346 : : "0388ce2529c809f21008959fd3b2697f6f7cf5a116acd7a93f3b1c369aab0c7a3a",
347 : : "", 0},
348 : : {ChainType::MAIN,
349 : : "sp1pq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68t02m0h0s8cwhy",
350 : : "0295fa435f2d74f73b4baaec28115df0b32e0f1b10a340ade948c56a647fe92cdc",
351 : : "0388ce2529c809f21008959fd3b2697f6f7cf5a116acd7a93f3b1c369aab0c7a3a",
352 : : "deadbeef", 1},
353 : : {ChainType::TESTNET4,
354 : : "tsp1qqthpye3hdcnydp9temp7yduy6uw5h2nw8u9fz677ccrna280qwj3uq60zeqs3zfpj3age62h4ljq2lyawwdecmk8a545yysk4x3tu3skjqm2thu6",
355 : : "02ee1266376e264684abcec3e23784d71d4baa6e3f0a916bdec6073ea8ef03a51e",
356 : : "034f1641088921947a8ce957afe4057c9d739b9c6ec7ed2b421216a9a2be461690",
357 : : "", 0},
358 : : {ChainType::SIGNET,
359 : : "tsp1qqvrl2accqtatgtkllv5fdsfapq6vqnrr0uf2whhmjyas4teg7ljfqqlpec0ze90m97t3stv92p5ekzkcwzmpx0x8p7ljj9xqcjjpvnfurc00tjn9",
360 : : "0307f5771802fab42edffb2896c13d0834c04c637f12a75efb913b0aaf28f7e490",
361 : : "03e1ce1e2c95fb2f97182d8550699b0ad870b6133cc70fbf2914c0c4a4164d3c1e",
362 : : "", 0},
363 : : {ChainType::REGTEST,
364 : : "sprt1qq04xgllnqqfdlxjkr355rmsahfn9t8l6sd0k20t4uka4c73nvvpnwqu4kvg0nm62d5jtmqp54lkc7tul0dzt25ejtn4f80505z3u0h5jag59rdg4",
365 : : "03ea647ff30012df9a561c6941ee1dba66559ffa835f653d75e5bb5c7a33630337",
366 : : "0395b310f9ef4a6d24bd8034afed8f2f9f7b44b553325cea93be8fa0a3c7de92ea",
367 : : "", 0},
368 [ + - + - : 6 : };
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- - - - -
- - - - -
- - - - -
- - - - ]
369 : :
370 [ + + ]: 6 : for (const auto& vec : valid_vectors) {
371 [ + - ]: 5 : SelectParams(vec.chain);
372 : :
373 [ + - + - ]: 5 : auto sp = DecodeSilentPaymentsAddress(vec.address, Params());
374 [ + - + - : 10 : BOOST_REQUIRE_MESSAGE(sp.has_value(), vec.address);
+ - ]
375 [ + - + - ]: 5 : BOOST_CHECK_EQUAL(sp->GetVersion(), vec.version);
376 [ + - + - : 5 : BOOST_CHECK_EQUAL(HexStr(sp->GetScanPubKey()), vec.scan_pubkey);
+ - ]
377 [ + - + - : 5 : BOOST_CHECK_EQUAL(HexStr(sp->GetSpendPubKey()), vec.spend_pubkey);
+ - ]
378 [ + - - + : 5 : BOOST_CHECK_EQUAL(HexStr(sp->GetExtensionData()), vec.extension_data);
+ - + - ]
379 : :
380 : : // Bech32(m) is case-insensitive as a whole; an all-uppercase address must decode identically.
381 [ - + + - ]: 5 : std::string flipped = ToUpper(vec.address);
382 [ + - + - ]: 5 : auto sp_flipped = DecodeSilentPaymentsAddress(flipped, Params());
383 [ + - + - : 10 : BOOST_REQUIRE_MESSAGE(sp_flipped.has_value(), flipped);
+ - ]
384 [ + - + - ]: 10 : BOOST_CHECK(*sp_flipped == *sp);
385 : 5 : }
386 : :
387 : : // `chain` is the one chain (if any) whose HRP matches the address, where `expected_error`
388 : : // applies; on every other chain the HRP check itself is expected to fail first.
389 : : // `chain_independent` addresses fail bech32m checksum verification before the HRP is even
390 : : // compared, so `expected_error` applies on every chain instead.
391 : 9 : struct InvalidVector {
392 : : std::string address;
393 : : std::string expected_error;
394 : : std::optional<ChainType> chain = std::nullopt;
395 : : bool chain_independent = false;
396 : : };
397 : 1 : const InvalidVector invalid_vectors[]{
398 : : {"spx1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68g37pn04",
399 : : "Invalid or unsupported prefix for Silent Payments address (expected sp, got spx)."}, // wrong HRP; never matches any chain
400 : : {"sp1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68gcwu9kg",
401 : : "Silent Payments address must use Bech32m checksum", std::nullopt, /*chain_independent=*/true}, // bad checksum
402 : : {"sp1qqgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq2qugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68g25havg",
403 : : "Invalid Silent payments address", ChainType::MAIN}, // invalid scan pubkey
404 : : {"sp1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq57qc9nf",
405 : : "Invalid Silent payments address", ChainType::MAIN}, // invalid spend pubkey
406 : : {"sp1lq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68gaafydt",
407 : : "This implementation only supports Silent payments addresses v0 through v30 (got 31).", ChainType::MAIN}, // reserved version 31
408 : : {"sp1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcznsxsn",
409 : : "Silent payments data payload is too small (expected at least 66, got 33).", ChainType::MAIN}, // payload too small
410 : : {"tsp1qqthpye3hdcnydp9temp7yduy6uw5h2nw8u9fz677ccrna280qwj3uq60zeqs3zfpj3age62h4ljq2lyawwdecmk8a545yysk4x3tu3skjqm2thum",
411 : : "Silent Payments address must use Bech32m checksum", std::nullopt, /*chain_independent=*/true}, // bad checksum
412 : : {"sp1qqgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf26rn7",
413 : : "Silent Payments address must use Bech32m checksum", std::nullopt, /*chain_independent=*/true}, // bad checksum
414 : : {"sprt1qqgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq2qu4kvg0nm62d5jtmqp54lkc7tul0dzt25ejtn4f80505z3u0h5jagf9zkp5",
415 : : "Invalid Silent payments address", ChainType::REGTEST}, // invalid scan pubkey
416 [ + - + - : 19 : };
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - - - ]
417 : :
418 [ + + ]: 10 : for (const auto& vec : invalid_vectors) {
419 [ + + ]: 54 : for (const auto chain : {ChainType::MAIN, ChainType::TESTNET, ChainType::TESTNET4, ChainType::SIGNET, ChainType::REGTEST}) {
420 [ + - ]: 45 : SelectParams(chain);
421 [ + - + - ]: 45 : auto sp = DecodeSilentPaymentsAddress(vec.address, Params());
422 [ + - + - : 90 : BOOST_REQUIRE_MESSAGE(!sp.has_value(), vec.address);
+ + ]
423 [ + + + + ]: 45 : if (vec.chain_independent || chain == vec.chain) {
424 [ + - + - ]: 20 : BOOST_CHECK_EQUAL(sp.error(), vec.expected_error);
425 : : } else {
426 [ + - - + : 70 : BOOST_CHECK_MESSAGE(sp.error().starts_with("Invalid or unsupported prefix for Silent Payments address"), sp.error());
+ - ]
427 : : }
428 : 45 : }
429 : : }
430 : :
431 [ + - ]: 1 : SelectParams(ChainType::MAIN);
432 [ + + + + : 17 : }
- - - - ]
433 : :
434 : : BOOST_AUTO_TEST_SUITE_END()
435 : : } // namespace bip352
|