LCOV - code coverage report
Current view: top level - src/test - bip352_tests.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 100.0 % 252 252
Test Date: 2026-10-11 07:59:32 Functions: 100.0 % 16 16
Branches: 51.2 % 1636 837

             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
        

Generated by: LCOV version 2.0-1