Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2018-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <pubkey.h>6#include <script/descriptor.h>7#include <script/sign.h>8#include <test/util/setup_common.h>9#include <util/check.h>10#include <util/strencodings.h>11#include <util/string.h>13#include <boost/test/unit_test.hpp>15#include <optional>16#include <regex>17#include <string>18#include <vector>20using namespace util::hex_literals;21using util::Split;23namespace {25void CheckUnparsable(const std::string& prv, const std::string& pub, const std::string& expected_error)26{27FlatSigningProvider keys_priv, keys_pub;28std::string error;29auto parse_priv = Parse(prv, keys_priv, error);30auto parse_pub = Parse(pub, keys_pub, error);31BOOST_CHECK_MESSAGE(parse_priv.empty(), prv);32BOOST_CHECK_MESSAGE(parse_pub.empty(), pub);33BOOST_CHECK_EQUAL(error, expected_error);34}36/** Check that the script is inferred as non-standard */37void CheckInferRaw(const CScript& script)38{39FlatSigningProvider dummy_provider;40std::unique_ptr<Descriptor> desc = InferDescriptor(script, dummy_provider);41BOOST_CHECK(desc->ToString().rfind("raw(", 0) == 0);42}44constexpr int DEFAULT = 0;45constexpr int RANGE = 1 << 0; // Expected to be ranged descriptor46constexpr int HARDENED = 1 << 1; // Derivation needs access to private keys47constexpr int UNSOLVABLE = 1 << 2; // This descriptor is not expected to be solvable48constexpr int SIGNABLE = 1 << 3; // We can sign with this descriptor (this is not true when actual BIP32 derivation is used, as that's not integrated in our signing code)49constexpr int DERIVE_HARDENED = 1 << 4; // The final derivation is hardened, i.e. ends with *' or *h50constexpr int MIXED_PUBKEYS = 1 << 5;51constexpr int XONLY_KEYS = 1 << 6; // X-only pubkeys are in use (and thus inferring/caching may swap parity of pubkeys/keyids)52constexpr int MISSING_PRIVKEYS = 1 << 7; // Not all private keys are available. ToPrivateString() will return true if there is at least one private key and HavePrivateKeys() will return `false`.53constexpr int SIGNABLE_FAILS = 1 << 8; // We can sign with this descriptor, but actually trying to sign will fail54constexpr int MUSIG = 1 << 9; // This is a MuSig so key counts will have an extra key55constexpr int MUSIG_DERIVATION = 1 << 10; // MuSig with BIP 328 derivation from the aggregate key56constexpr int MIXED_MUSIG = 1 << 11; // Both MuSig and normal key expressions are present57constexpr int UNIQUE_XPUBS = 1 << 12; // Whether the xpub count should be of unique xpubs59/** Compare two descriptors. If only one of them has a checksum, the checksum is ignored. */60bool EqualDescriptor(std::string a, std::string b)61{62bool a_check = (a.size() > 9 && a[a.size() - 9] == '#');63bool b_check = (b.size() > 9 && b[b.size() - 9] == '#');64if (a_check != b_check) {65if (a_check) a = a.substr(0, a.size() - 9);66if (b_check) b = b.substr(0, b.size() - 9);67}68return a == b;69}71bool EqualSigningProviders(const FlatSigningProvider& a, const FlatSigningProvider& b)72{73return a.scripts == b.scripts74&& a.pubkeys == b.pubkeys75&& a.origins == b.origins76&& a.keys == b.keys77&& a.tr_trees == b.tr_trees;78}80std::string UseHInsteadOfApostrophe(const std::string& desc)81{82std::string ret = desc;83while (true) {84auto it = ret.find('\'');85if (it == std::string::npos) break;86ret[it] = 'h';87}89// GetDescriptorChecksum returns "" if the checksum exists but is bad.90// Switching apostrophes with 'h' breaks the checksum if it exists - recalculate it and replace the broken one.91if (GetDescriptorChecksum(ret) == "") {92ret = ret.substr(0, desc.size() - 9);93ret += std::string("#") + GetDescriptorChecksum(ret);94}95return ret;96}98// Count the number of times the string "xpub" appears in a descriptor string99static size_t CountXpubs(const std::string& desc)100{101size_t count = 0;102size_t p = desc.find("xpub", 0);103while (p != std::string::npos) {104count++;105p = desc.find("xpub", p + 1);106}107return count;108}109//110// Count the number of unique xpubs in a descriptor string111static size_t CountUniqueXpubs(const std::string& desc)112{113std::regex xpub_regex("xpub\\w+?(?=/)");114auto search_begin = std::sregex_iterator(desc.begin(), desc.end(), xpub_regex);115auto search_end = std::sregex_iterator();116std::set<std::string> xpubs;117for (std::regex_iterator i = search_begin; i != search_end; ++i) {118xpubs.emplace(i->str());119}120return xpubs.size();121}123const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}};125std::set<CPubKey> GetKeyData(const FlatSigningProvider& provider, int flags) {126std::set<CPubKey> ret;127for (const auto& [_, pubkey] : provider.pubkeys) {128if (flags & XONLY_KEYS) {129unsigned char bytes[33];130BOOST_CHECK_EQUAL(pubkey.size(), 33);131std::copy(pubkey.begin(), pubkey.end(), bytes);132bytes[0] = 0x02;133CPubKey norm_pubkey{bytes};134ret.insert(norm_pubkey);135} else {136ret.insert(pubkey);137}138}139return ret;140}142std::set<std::pair<CPubKey, KeyOriginInfo>> GetKeyOriginData(const FlatSigningProvider& provider, int flags) {143std::set<CKeyID> ignored;144if (flags & MUSIG) {145for (const auto& [_, part_pks] : provider.aggregate_pubkeys) {146for (const auto& pk : part_pks) {147ignored.insert(pk.GetID());148}149}150}152std::set<std::pair<CPubKey, KeyOriginInfo>> ret;153for (const auto& [keyid, data] : provider.origins) {154if (ignored.contains(keyid)) continue;155if (flags & XONLY_KEYS) {156unsigned char bytes[33];157BOOST_CHECK_EQUAL(data.first.size(), 33);158std::copy(data.first.begin(), data.first.end(), bytes);159bytes[0] = 0x02;160CPubKey norm_pubkey{bytes};161KeyOriginInfo norm_origin = data.second;162norm_origin.fingerprint.fill(0); // fingerprints don't necessarily match.163ret.emplace(norm_pubkey, norm_origin);164} else {165ret.insert(data);166}167}168return ret;169}171void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int flags,172const std::vector<std::vector<std::string>>& scripts, const std::optional<OutputType>& type, std::optional<uint256> op_compat_desc_hash = std::nullopt,173const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY, bool replace_apostrophe_with_h_in_prv=false,174bool replace_apostrophe_with_h_in_pub=false, uint32_t spender_nlocktime=0, uint32_t spender_nsequence=CTxIn::SEQUENCE_FINAL,175std::map<std::vector<uint8_t>, std::vector<uint8_t>> preimages={},176std::optional<std::string> expected_prv = std::nullopt, std::optional<std::string> expected_pub = std::nullopt, int desc_index = 0)177{178FlatSigningProvider keys_priv, keys_pub;179std::set<std::vector<uint32_t>> left_paths = paths;180std::string error;182std::vector<std::unique_ptr<Descriptor>> parse_privs;183std::vector<std::unique_ptr<Descriptor>> parse_pubs;184// Check that parsing succeeds.185if (replace_apostrophe_with_h_in_prv) {186prv = UseHInsteadOfApostrophe(prv);187}188parse_privs = Parse(prv, keys_priv, error);189BOOST_CHECK_MESSAGE(!parse_privs.empty(), error);190if (replace_apostrophe_with_h_in_pub) {191pub = UseHInsteadOfApostrophe(pub);192}193parse_pubs = Parse(pub, keys_pub, error);194BOOST_CHECK_MESSAGE(!parse_pubs.empty(), error);196auto& parse_priv = parse_privs.at(desc_index);197auto& parse_pub = parse_pubs.at(desc_index);199// We must be able to estimate the max satisfaction size for any solvable descriptor top descriptor (but combo).200const bool is_nontop_or_nonsolvable{!parse_priv->IsSolvable() || !parse_priv->GetOutputType()};201const auto max_sat_maxsig{parse_priv->MaxSatisfactionWeight(true)};202const auto max_sat_nonmaxsig{parse_priv->MaxSatisfactionWeight(false)};203BOOST_CHECK(max_sat_nonmaxsig <= max_sat_maxsig);204const auto max_elems{parse_priv->MaxSatisfactionElems()};205const bool is_input_size_info_set{max_sat_maxsig && max_sat_nonmaxsig && max_elems};206BOOST_CHECK_MESSAGE(is_input_size_info_set || is_nontop_or_nonsolvable, prv);208// The ScriptSize() must match the size of the Script string. (ScriptSize() is set for all descs but 'combo()'.)209const bool is_combo{!parse_priv->IsSingleType()};210BOOST_CHECK_MESSAGE(is_combo || parse_priv->ScriptSize() == scripts[0][0].size() / 2, "Invalid ScriptSize() for " + prv);212// Check that the correct OutputType is inferred213BOOST_CHECK(parse_priv->GetOutputType() == type);214BOOST_CHECK(parse_pub->GetOutputType() == type);216// Check private keys are extracted from the private version but not the public one.217BOOST_CHECK(keys_priv.keys.size());218BOOST_CHECK(!keys_pub.keys.size());219const bool have_all_private_keys = !(flags & MISSING_PRIVKEYS);220BOOST_CHECK_EQUAL(parse_priv->HavePrivateKeys(keys_priv), have_all_private_keys);221BOOST_CHECK_EQUAL(parse_pub->HavePrivateKeys(keys_priv), have_all_private_keys);222BOOST_CHECK(!parse_priv->HavePrivateKeys(keys_pub));223BOOST_CHECK(!parse_pub->HavePrivateKeys(keys_pub));225// If expected_pub is provided, check that the serialize matches that.226// Otherwise check that they serialize back to the public version.227std::string pub1 = parse_priv->ToString();228std::string pub2 = parse_pub->ToString();229std::string canonical = UseHInsteadOfApostrophe(pub);230if (expected_pub) {231BOOST_CHECK_MESSAGE(EqualDescriptor(*expected_pub, pub1), "Private ser: " + pub1 + " Public desc: " + *expected_pub);232BOOST_CHECK_MESSAGE(EqualDescriptor(*expected_pub, pub2), "Public ser: " + pub2 + " Public desc: " + *expected_pub);233canonical = UseHInsteadOfApostrophe(*expected_pub);234} else {235BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub1), "Private ser: " + pub1 + " Public desc: " + pub);236BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub2), "Public ser: " + pub2 + " Public desc: " + pub);237}239std::string priv_canonical = parse_priv->ToCanonicalString();240std::string pub_canonical = parse_pub->ToCanonicalString();241BOOST_CHECK_MESSAGE(EqualDescriptor(priv_canonical, canonical), "Private ser: " + priv_canonical + " Expected desc: " + canonical);242BOOST_CHECK_MESSAGE(EqualDescriptor(pub_canonical, canonical), "Public ser: " + pub_canonical + " Expected desc: " + canonical);244// Check that the backwards compatibility descriptor hash did not change245if (op_compat_desc_hash) {246BOOST_CHECK_MESSAGE(CompatDescriptorHash(*parse_priv) == *op_compat_desc_hash, "CompatDescriptorHash() " + CompatDescriptorHash(*parse_priv).ToString() + " does not match for priv " + prv);247}249// Check that both can be serialized with private key back to the private version, but not without private key.250if (!(flags & MISSING_PRIVKEYS)) {
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.