6#include <chainparams.h>
11#include <test/data/bip352_send_and_receive_vectors.json.h>
15#include <boost/test/unit_test.hpp>
27 std::vector<unsigned char> hex_data =
ParseHex(hex);
28 output.
Set(hex_data.begin(), hex_data.end(),
true);
35 BOOST_REQUIRE(
tests.read(json_tests::bip352_send_and_receive_vectors));
37 for (
const auto& vec :
tests.getValues()) {
39 BOOST_TEST_MESSAGE(vec[
"comment"].get_str());
41 for (
const auto& sender : vec[
"sending"].getValues()) {
42 const UniValue& given = sender[
"given"];
43 const UniValue& expected = sender[
"expected"];
45 std::vector<COutPoint> outpoints;
46 std::vector<CKey>
keys;
47 std::vector<KeyPair> taproot_keys;
48 for (
const auto& input : given[
"vin"].getValues()) {
50 outpoints.push_back(outpoint);
51 const auto& spk_bytes =
ParseHex(input[
"prevout"][
"scriptPubKey"][
"hex"].get_str());
53 const auto& script_sig_bytes =
ParseHex(input[
"scriptSig"].get_str());
54 CScript script_sig =
CScript(script_sig_bytes.begin(), script_sig_bytes.end());
55 CTxIn txin{outpoint, script_sig};
57 const auto witness_str =
ParseHex(input[
"txinwitness"].get_str());
58 if (!witness_str.empty()) {
59 SpanReader(witness_str) >> txin.scriptWitness.stack;
64 if (pubkey.has_value()) {
65 std::vector<std::vector<unsigned char>> solutions;
68 taproot_keys.emplace_back(
ParseHexToCKey(input[
"private_key"].get_str()).ComputeKeyPair(
nullptr));
74 if (taproot_keys.empty() &&
keys.empty()) {
75 BOOST_CHECK(expected[
"outputs"].getValues()[0].empty());
80 std::map<size_t, SilentPaymentsDestination> sp_dests;
81 const std::vector<UniValue>& silent_payments_addresses = given[
"recipients"].
getValues();
83 for (
size_t i = 0; i < silent_payments_addresses.size(); ++i) {
85 BOOST_REQUIRE(sp.has_value());
86 if (!silent_payments_addresses[i][
"scan_pub_key"].isNull()) {
90 size_t count = silent_payments_addresses[i][
"count"].isNull() ? 1 : (size_t)silent_payments_addresses[i][
"count"].getInt<int>();
91 for (
size_t j = 0; j <
count; ++j) {
92 sp_dests.emplace(sp_index++, *sp);
97 if (!sp_tr_dests.has_value()) {
99 BOOST_CHECK(expected[
"outputs"].getValues()[0].empty());
103 for (
const auto& candidate_set : expected[
"outputs"].getValues()) {
104 BOOST_CHECK(sp_tr_dests->size() == candidate_set.size());
105 std::vector<WitnessV1Taproot> expected_spks;
106 for (
const auto& output : candidate_set.getValues()) {
108 expected_spks.push_back(tap);
110 match = std::all_of(sp_tr_dests->begin(), sp_tr_dests->end(), [&](
const auto& entry) {
111 return std::find(expected_spks.begin(), expected_spks.end(), entry.second) != expected_spks.end();
119 for (
const auto& recipient : vec[
"receiving"].getValues()) {
121 const UniValue& given = recipient[
"given"];
122 const UniValue& expected = recipient[
"expected"];
124 std::vector<CTxIn> vin;
125 std::map<COutPoint, Coin> coins;
126 for (
const auto& input : given[
"vin"].getValues()) {
128 const auto& spk_bytes =
ParseHex(input[
"prevout"][
"scriptPubKey"][
"hex"].get_str());
130 const auto& script_sig_bytes =
ParseHex(input[
"scriptSig"].get_str());
131 CScript script_sig =
CScript(script_sig_bytes.begin(), script_sig_bytes.end());
132 CTxIn txin{outpoint, script_sig};
134 const auto witness_str =
ParseHex(input[
"txinwitness"].get_str());
135 if (!witness_str.empty()) {
136 SpanReader(witness_str) >> txin.scriptWitness.stack;
139 coins[outpoint] =
Coin{
CTxOut{{}, spk}, 0,
false};
144 if (!pub_tweak_data.has_value()) {
149 std::vector<XOnlyPubKey> output_pub_keys;
150 for (
const auto& pubkey : given[
"outputs"].getValues()) {
151 output_pub_keys.emplace_back(
ParseHex(pubkey.get_str()));
154 CKey scan_priv_key =
ParseHexToCKey(given[
"key_material"][
"scan_priv_key"].get_str());
155 CKey spend_priv_key =
ParseHexToCKey(given[
"key_material"][
"spend_priv_key"].get_str());
158 BOOST_REQUIRE(expected_address.has_value());
163 auto given_labels{given[
"labels"].
getValues()};
164 for (
size_t i = 0; i < given_labels.size(); i++) {
165 const uint32_t
m = given_labels[i].getInt<uint32_t>();
170 BOOST_REQUIRE(sp.has_value());
175 const auto& found_outputs = receiver.Scan(*pub_tweak_data, output_pub_keys);
176 BOOST_REQUIRE(found_outputs.has_value());
179 if (found_outputs->empty()) {
183 if (!expected[
"n_outputs"].isNull()) {
186 std::map<XOnlyPubKey, uint256> expected_outputs;
187 for (
const auto& output : expected[
"outputs"].getValues()) {
188 expected_outputs.emplace(
192 BOOST_TEST_MESSAGE(found_outputs->size());
193 BOOST_REQUIRE_EQUAL(found_outputs->size(), expected_outputs.size());
194 for (
const auto& output : *found_outputs) {
195 const auto expected_output = expected_outputs.find(output.output);
196 BOOST_REQUIRE(expected_output != expected_outputs.end());
197 BOOST_CHECK(output.tweak == expected_output->second);
206 CKey sender_key =
ParseHexToCKey(
"0000000000000000000000000000000000000000000000000000000000000001");
207 CKey scan_key =
ParseHexToCKey(
"0000000000000000000000000000000000000000000000000000000000000002");
208 CKey spend_key =
ParseHexToCKey(
"0000000000000000000000000000000000000000000000000000000000000003");
210 std::map<size_t, SilentPaymentsDestination> sp_dests{{2, sp_dest}, {5, sp_dest}};
215 BOOST_REQUIRE(generated.has_value());
225 CKey key =
ParseHexToCKey(
"0000000000000000000000000000000000000000000000000000000000000001");
227 COutPoint eligible_outpoint{
Txid::FromHex(
"0000000000000000000000000000000000000000000000000000000000000001").value(), 0};
228 COutPoint unknown_segwit_outpoint{
Txid::FromHex(
"0000000000000000000000000000000000000000000000000000000000000002").value(), 0};
230 CTxIn eligible_input{eligible_outpoint};
232 eligible_input.scriptWitness.stack.emplace_back(pubkey.
begin(), pubkey.
end());
234 std::map<COutPoint, Coin> coins;
244 CKey sender_key =
ParseHexToCKey(
"0000000000000000000000000000000000000000000000000000000000000001");
245 CKey scan_key =
ParseHexToCKey(
"0000000000000000000000000000000000000000000000000000000000000002");
246 CKey spend_key =
ParseHexToCKey(
"0000000000000000000000000000000000000000000000000000000000000003");
247 const COutPoint outpoint{
Txid::FromHex(
"0000000000000000000000000000000000000000000000000000000000000001").value(), 0};
249 std::map<size_t, SilentPaymentsDestination> sp_dests;
252 BOOST_REQUIRE(sp_tr_dests.has_value());
255 CTxIn txin{outpoint};
257 txin.scriptWitness.stack.emplace_back();
258 txin.scriptWitness.stack.emplace_back(sender_pubkey.begin(), sender_pubkey.end());
260 std::map<COutPoint, Coin> coins;
263 BOOST_REQUIRE(prevouts_summary.has_value());
265 std::vector<XOnlyPubKey> output_pub_keys;
266 output_pub_keys.emplace_back(
ParseHex(
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
267 output_pub_keys.push_back(expected_output);
270 const auto found_outputs = receiver.Scan(*prevouts_summary, output_pub_keys);
271 BOOST_REQUIRE(found_outputs.has_value());
272 BOOST_REQUIRE_EQUAL(found_outputs->size(), 1);
273 BOOST_CHECK(found_outputs->front().output == expected_output);
278 CKey scan_key =
ParseHexToCKey(
"0000000000000000000000000000000000000000000000000000000000000002");
282 BOOST_REQUIRE_EQUAL(receiver.GetLabels().size(), 1);
288 BOOST_REQUIRE(roundtripped.has_value());
298 std::string scan_pubkey;
299 std::string spend_pubkey;
300 std::string extension_data;
303 const ValidVector valid_vectors[]{
305 "sp1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68gdjvfn2",
306 "0295fa435f2d74f73b4baaec28115df0b32e0f1b10a340ade948c56a647fe92cdc",
307 "0388ce2529c809f21008959fd3b2697f6f7cf5a116acd7a93f3b1c369aab0c7a3a",
310 "sp1pq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68t02m0h0s8cwhy",
311 "0295fa435f2d74f73b4baaec28115df0b32e0f1b10a340ade948c56a647fe92cdc",
312 "0388ce2529c809f21008959fd3b2697f6f7cf5a116acd7a93f3b1c369aab0c7a3a",
315 "tsp1qqthpye3hdcnydp9temp7yduy6uw5h2nw8u9fz677ccrna280qwj3uq60zeqs3zfpj3age62h4ljq2lyawwdecmk8a545yysk4x3tu3skjqm2thu6",
316 "02ee1266376e264684abcec3e23784d71d4baa6e3f0a916bdec6073ea8ef03a51e",
317 "034f1641088921947a8ce957afe4057c9d739b9c6ec7ed2b421216a9a2be461690",
320 "tsp1qqvrl2accqtatgtkllv5fdsfapq6vqnrr0uf2whhmjyas4teg7ljfqqlpec0ze90m97t3stv92p5ekzkcwzmpx0x8p7ljj9xqcjjpvnfurc00tjn9",
321 "0307f5771802fab42edffb2896c13d0834c04c637f12a75efb913b0aaf28f7e490",
322 "03e1ce1e2c95fb2f97182d8550699b0ad870b6133cc70fbf2914c0c4a4164d3c1e",
325 "sprt1qq04xgllnqqfdlxjkr355rmsahfn9t8l6sd0k20t4uka4c73nvvpnwqu4kvg0nm62d5jtmqp54lkc7tul0dzt25ejtn4f80505z3u0h5jag59rdg4",
326 "03ea647ff30012df9a561c6941ee1dba66559ffa835f653d75e5bb5c7a33630337",
327 "0395b310f9ef4a6d24bd8034afed8f2f9f7b44b553325cea93be8fa0a3c7de92ea",
331 for (
const auto& vec : valid_vectors) {
335 BOOST_REQUIRE_MESSAGE(sp.has_value(), vec.address);
342 std::string flipped =
ToUpper(vec.address);
344 BOOST_REQUIRE_MESSAGE(sp_flipped.has_value(), flipped);
352 struct InvalidVector {
354 std::string expected_error;
355 std::optional<ChainType> chain = std::nullopt;
356 bool chain_independent =
false;
358 const InvalidVector invalid_vectors[]{
359 {
"spx1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68g37pn04",
360 "Invalid or unsupported prefix for Silent Payments address (expected sp, got spx)."},
361 {
"sp1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68gcwu9kg",
362 "Silent Payments address must use Bech32m checksum", std::nullopt,
true},
363 {
"sp1qqgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq2qugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68g25havg",
365 {
"sp1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq57qc9nf",
367 {
"sp1lq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcqugecjjnjqf7ggq39vl6wexjlm00n66z94v675n7wcux6d2krr68gaafydt",
368 "This implementation only supports Silent payments addresses v0 through v30 (got 31).",
ChainType::MAIN},
369 {
"sp1qq22l5s6l9460ww6t4tkzsy2a7zejurcmzz35pt0ffrzk5erlaykdcznsxsn",
370 "Silent payments data payload is too small (expected at least 66, got 33).",
ChainType::MAIN},
371 {
"tsp1qqthpye3hdcnydp9temp7yduy6uw5h2nw8u9fz677ccrna280qwj3uq60zeqs3zfpj3age62h4ljq2lyawwdecmk8a545yysk4x3tu3skjqm2thum",
372 "Silent Payments address must use Bech32m checksum", std::nullopt,
true},
373 {
"sp1qqgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqf26rn7",
374 "Silent Payments address must use Bech32m checksum", std::nullopt,
true},
375 {
"sprt1qqgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq2qu4kvg0nm62d5jtmqp54lkc7tul0dzt25ejtn4f80505z3u0h5jagf9zkp5",
379 for (
const auto& vec : invalid_vectors) {
383 BOOST_REQUIRE_MESSAGE(!sp.has_value(), vec.address);
384 if (vec.chain_independent || chain == vec.chain) {
387 BOOST_CHECK_MESSAGE(sp.error().starts_with(
"Invalid or unsupported prefix for Silent Payments address"), sp.error());
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
void SelectParams(const ChainType chain)
Sets the params returned by Params() to those for the given chain type.
const CChainParams & Params()
Return the currently selected parameters.
An encapsulated private key.
CPubKey GetPubKey() const
Compute the public key from a private key.
void Set(const T pbegin, const T pend, bool fCompressedIn)
Initialize using begin and end iterators to byte data.
An outpoint - a combination of a transaction hash and an index n into its vout.
An encapsulated public key.
const unsigned char * end() const
const unsigned char * begin() const
Serialized script, used inside transaction inputs and outputs.
An input of a transaction.
CScriptWitness scriptWitness
Only serialized through CTransaction.
An output of a transaction.
Double ended buffer combining vector and stream-like interfaces.
Minimal stream for reading from an existing byte array by std::span.
const std::vector< UniValue > & getValues() const
const unsigned char * begin() const
static std::optional< SilentPaymentsLabel > Unserialize(Stream &s)
void Serialize(Stream &s) const
A silent payments recipient's scanning identity.
static std::optional< transaction_identifier > FromHex(std::string_view hex)
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value")
util::Expected< PrevoutsSummary, PrevoutsSummaryError > GetSilentPaymentsPrevoutsSummary(const std::vector< CTxIn > &vin, const std::map< COutPoint, Coin > &coins)
Get silent payments public data from transaction inputs.
std::optional< PubKey > GetPubKeyFromInput(const CTxIn &txin, const CScript &spk)
Get the public key from an input.
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)
Generate silent payments taproot destinations.
CKey ParseHexToCKey(std::string_view hex)
util::Expected< SilentPaymentsDestination, std::string > DecodeSilentPaymentsAddress(const std::string &str, const CChainParams ¶ms)
Decode a BIP352 "sp1..." address. Returns the destination, or an error message on failure.
BOOST_AUTO_TEST_CASE(bip352_send_and_receive_test_vectors)
#define BOOST_CHECK(expr)
static unsigned char smallest_outpoint[36]
TxoutType Solver(const CScript &scriptPubKey, std::vector< std::vector< unsigned char > > &vSolutionsRet)
Parse a scriptPubKey and identify script type for standard scripts.
std::vector< Byte > ParseHex(std::string_view hex_str)
Like TryParseHex, but returns an empty vector on invalid input.
std::vector< std::vector< unsigned char > > stack
CTxDestination subtype to encode any future Witness version.
static std::optional< SilentPaymentsDestination > From(const CPubKey &scan_pubkey, const CPubKey &spend_pubkey, uint8_t version=0, std::span< const unsigned char > extension_data={})
std::vector< uint16_t > keys
constexpr std::array tests
std::string ToUpper(std::string_view str)
Returns the uppercase equivalent of the given string.