diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bitcoin-cli.cpp | 6 | ||||
-rw-r--r-- | src/bitcoin-util.cpp | 45 | ||||
-rw-r--r-- | src/bitcoin-wallet.cpp | 10 | ||||
-rw-r--r-- | src/coins.cpp | 4 | ||||
-rw-r--r-- | src/coins.h | 6 | ||||
-rw-r--r-- | src/net_processing.cpp | 2 | ||||
-rw-r--r-- | src/node/interfaces.cpp | 2 | ||||
-rw-r--r-- | src/outputtype.cpp | 23 | ||||
-rw-r--r-- | src/outputtype.h | 5 | ||||
-rw-r--r-- | src/rpc/blockchain.cpp | 4 | ||||
-rw-r--r-- | src/rpc/misc.cpp | 3 | ||||
-rw-r--r-- | src/script/descriptor.cpp | 92 | ||||
-rw-r--r-- | src/script/interpreter.cpp | 32 | ||||
-rw-r--r-- | src/script/interpreter.h | 6 | ||||
-rw-r--r-- | src/script/standard.cpp | 135 | ||||
-rw-r--r-- | src/script/standard.h | 8 | ||||
-rw-r--r-- | src/test/fuzz/coins_view.cpp | 4 | ||||
-rw-r--r-- | src/txdb.cpp | 29 | ||||
-rw-r--r-- | src/txdb.h | 24 | ||||
-rw-r--r-- | src/util/system.cpp | 4 | ||||
-rw-r--r-- | src/util/system.h | 2 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 9 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 24 | ||||
-rw-r--r-- | src/wallet/scriptpubkeyman.cpp | 29 | ||||
-rw-r--r-- | src/wallet/scriptpubkeyman.h | 13 | ||||
-rw-r--r-- | src/wallet/spend.cpp | 5 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 25 | ||||
-rw-r--r-- | src/wallet/wallet.h | 2 |
28 files changed, 425 insertions, 128 deletions
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index adc485983f..7a5f945511 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -797,7 +797,7 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str // Execute and handle connection failures with -rpcwait. const bool fWait = gArgs.GetBoolArg("-rpcwait", false); const int timeout = gArgs.GetArg("-rpcwaittimeout", DEFAULT_WAIT_CLIENT_TIMEOUT); - const int64_t deadline = GetTime<std::chrono::seconds>().count() + timeout; + const auto deadline{GetTime<std::chrono::microseconds>() + 1s * timeout}; do { try { @@ -810,9 +810,9 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str } break; // Connection succeeded, no need to retry. } catch (const CConnectionFailed& e) { - const int64_t now = GetTime<std::chrono::seconds>().count(); + const auto now{GetTime<std::chrono::microseconds>()}; if (fWait && (timeout <= 0 || now < deadline)) { - UninterruptibleSleep(std::chrono::seconds{1}); + UninterruptibleSleep(1s); } else { throw CConnectionFailed(strprintf("timeout on transient error: %s", e.what())); } diff --git a/src/bitcoin-util.cpp b/src/bitcoin-util.cpp index 3f273e75f6..f534aecc19 100644 --- a/src/bitcoin-util.cpp +++ b/src/bitcoin-util.cpp @@ -7,28 +7,19 @@ #endif #include <arith_uint256.h> +#include <chain.h> +#include <chainparams.h> +#include <chainparamsbase.h> #include <clientversion.h> -#include <coins.h> -#include <consensus/consensus.h> #include <core_io.h> -#include <key_io.h> -#include <policy/rbf.h> -#include <primitives/transaction.h> -#include <script/script.h> -#include <script/sign.h> -#include <script/signingprovider.h> -#include <univalue.h> -#include <util/moneystr.h> -#include <util/rbf.h> -#include <util/strencodings.h> -#include <util/string.h> +#include <streams.h> #include <util/system.h> #include <util/translation.h> #include <atomic> +#include <cstdio> #include <functional> #include <memory> -#include <stdio.h> #include <thread> #include <boost/algorithm/string.hpp> @@ -43,29 +34,29 @@ static void SetupBitcoinUtilArgs(ArgsManager &argsman) argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddCommand("grind", "Perform proof of work on hex header string", OptionsCategory::COMMANDS); + argsman.AddCommand("grind", "Perform proof of work on hex header string"); SetupChainParamsBaseOptions(argsman); } // This function returns either one of EXIT_ codes when it's expected to stop the process or // CONTINUE_EXECUTION when it's expected to continue further. -static int AppInitUtil(int argc, char* argv[]) +static int AppInitUtil(ArgsManager& args, int argc, char* argv[]) { - SetupBitcoinUtilArgs(gArgs); + SetupBitcoinUtilArgs(args); std::string error; - if (!gArgs.ParseParameters(argc, argv, error)) { + if (!args.ParseParameters(argc, argv, error)) { tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error); return EXIT_FAILURE; } - if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { + if (HelpRequested(args) || args.IsArgSet("-version")) { // First part of help message is specific to this utility std::string strUsage = PACKAGE_NAME " bitcoin-util utility version " + FormatFullVersion() + "\n"; - if (!gArgs.IsArgSet("-version")) { + if (!args.IsArgSet("-version")) { strUsage += "\n" "Usage: bitcoin-util [options] [commands] Do stuff\n"; - strUsage += "\n" + gArgs.GetHelpMessage(); + strUsage += "\n" + args.GetHelpMessage(); } tfm::format(std::cout, "%s", strUsage); @@ -79,7 +70,7 @@ static int AppInitUtil(int argc, char* argv[]) // Check for chain settings (Params() calls are only valid after this clause) try { - SelectParams(gArgs.GetChainName()); + SelectParams(args.GetChainName()); } catch (const std::exception& e) { tfm::format(std::cerr, "Error: %s\n", e.what()); return EXIT_FAILURE; @@ -114,7 +105,7 @@ static void grind_task(uint32_t nBits, CBlockHeader& header_orig, uint32_t offse } } -static int Grind(std::vector<std::string> args, std::string& strPrint) +static int Grind(const std::vector<std::string>& args, std::string& strPrint) { if (args.size() != 1) { strPrint = "Must specify block header to grind"; @@ -160,12 +151,14 @@ __declspec(dllexport) int main(int argc, char* argv[]) int main(int argc, char* argv[]) #endif { + ArgsManager& args = gArgs; SetupEnvironment(); try { - int ret = AppInitUtil(argc, argv); - if (ret != CONTINUE_EXECUTION) + int ret = AppInitUtil(args, argc, argv); + if (ret != CONTINUE_EXECUTION) { return ret; + } } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInitUtil()"); return EXIT_FAILURE; @@ -174,7 +167,7 @@ int main(int argc, char* argv[]) return EXIT_FAILURE; } - const auto cmd = gArgs.GetCommand(); + const auto cmd = args.GetCommand(); if (!cmd) { tfm::format(std::cerr, "Error: must specify a command\n"); return EXIT_FAILURE; diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index b84d909b07..765954c92e 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -33,11 +33,11 @@ static void SetupWalletToolArgs(ArgsManager& argsman) argsman.AddArg("-format=<format>", "The format of the wallet file to create. Either \"bdb\" or \"sqlite\". Only used with 'createfromdump'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); - argsman.AddCommand("info", "Get wallet info", OptionsCategory::COMMANDS); - argsman.AddCommand("create", "Create new wallet file", OptionsCategory::COMMANDS); - argsman.AddCommand("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.", OptionsCategory::COMMANDS); - argsman.AddCommand("dump", "Print out all of the wallet key-value records", OptionsCategory::COMMANDS); - argsman.AddCommand("createfromdump", "Create new wallet file from dumped records", OptionsCategory::COMMANDS); + argsman.AddCommand("info", "Get wallet info"); + argsman.AddCommand("create", "Create new wallet file"); + argsman.AddCommand("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental."); + argsman.AddCommand("dump", "Print out all of the wallet key-value records"); + argsman.AddCommand("createfromdump", "Create new wallet file from dumped records"); } static bool WalletAppInit(ArgsManager& args, int argc, char* argv[]) diff --git a/src/coins.cpp b/src/coins.cpp index d52851cadd..ce0b131de6 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -13,7 +13,7 @@ bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return f uint256 CCoinsView::GetBestBlock() const { return uint256(); } std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); } bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; } -CCoinsViewCursor *CCoinsView::Cursor() const { return nullptr; } +std::unique_ptr<CCoinsViewCursor> CCoinsView::Cursor() const { return nullptr; } bool CCoinsView::HaveCoin(const COutPoint &outpoint) const { @@ -28,7 +28,7 @@ uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); } -CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); } +std::unique_ptr<CCoinsViewCursor> CCoinsViewBacked::Cursor() const { return base->Cursor(); } size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {} diff --git a/src/coins.h b/src/coins.h index 816b4864a3..3151a260d9 100644 --- a/src/coins.h +++ b/src/coins.h @@ -180,7 +180,7 @@ public: virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); //! Get a cursor to iterate over the whole state - virtual CCoinsViewCursor *Cursor() const; + virtual std::unique_ptr<CCoinsViewCursor> Cursor() const; //! As we use CCoinsViews polymorphically, have a virtual destructor virtual ~CCoinsView() {} @@ -204,7 +204,7 @@ public: std::vector<uint256> GetHeadBlocks() const override; void SetBackend(CCoinsView &viewIn); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; - CCoinsViewCursor *Cursor() const override; + std::unique_ptr<CCoinsViewCursor> Cursor() const override; size_t EstimateSize() const override; }; @@ -237,7 +237,7 @@ public: uint256 GetBestBlock() const override; void SetBestBlock(const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; - CCoinsViewCursor* Cursor() const override { + std::unique_ptr<CCoinsViewCursor> Cursor() const override { throw std::logic_error("CCoinsViewCache cursor iteration not supported."); } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 9bb3dfdcf3..2175b76d16 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -467,7 +467,7 @@ private: bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** Remove this block from our tracked requested blocks. Called if: - * - the block has been recieved from a peer + * - the block has been received from a peer * - the request for the block has timed out */ void RemoveBlockRequest(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index fce3c1809c..807b0143a6 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -180,7 +180,7 @@ public: ExternalSigner::Enumerate(command, signers, Params().NetworkIDString()); return signers; #else - // This result is undistinguisable from a succesful call that returns + // This result is indistinguishable from a successful call that returns // no signers. For the current GUI this doesn't matter, because the wallet // creation dialog disables the external signer checkbox in both // cases. The return type could be changed to std::optional<std::vector> diff --git a/src/outputtype.cpp b/src/outputtype.cpp index d96fb282c5..8ede7b9974 100644 --- a/src/outputtype.cpp +++ b/src/outputtype.cpp @@ -18,6 +18,7 @@ static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; +static const std::string OUTPUT_TYPE_STRING_BECH32M = "bech32m"; bool ParseOutputType(const std::string& type, OutputType& output_type) { @@ -30,6 +31,9 @@ bool ParseOutputType(const std::string& type, OutputType& output_type) } else if (type == OUTPUT_TYPE_STRING_BECH32) { output_type = OutputType::BECH32; return true; + } else if (type == OUTPUT_TYPE_STRING_BECH32M) { + output_type = OutputType::BECH32M; + return true; } return false; } @@ -40,6 +44,7 @@ const std::string& FormatOutputType(OutputType type) case OutputType::LEGACY: return OUTPUT_TYPE_STRING_LEGACY; case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32; + case OutputType::BECH32M: return OUTPUT_TYPE_STRING_BECH32M; } // no default case, so the compiler can warn about missing cases assert(false); } @@ -59,6 +64,7 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) return witdest; } } + case OutputType::BECH32M: {} // This function should never be used with BECH32M, so let it assert } // no default case, so the compiler can warn about missing cases assert(false); } @@ -98,6 +104,23 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, return ScriptHash(witprog); } } + case OutputType::BECH32M: {} // This function should not be used for BECH32M, so let it assert } // no default case, so the compiler can warn about missing cases assert(false); } + +std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) { + if (std::holds_alternative<PKHash>(dest) || + std::holds_alternative<ScriptHash>(dest)) { + return OutputType::LEGACY; + } + if (std::holds_alternative<WitnessV0KeyHash>(dest) || + std::holds_alternative<WitnessV0ScriptHash>(dest)) { + return OutputType::BECH32; + } + if (std::holds_alternative<WitnessV1Taproot>(dest) || + std::holds_alternative<WitnessUnknown>(dest)) { + return OutputType::BECH32M; + } + return std::nullopt; +} diff --git a/src/outputtype.h b/src/outputtype.h index 88422e5824..2b83235cd0 100644 --- a/src/outputtype.h +++ b/src/outputtype.h @@ -18,12 +18,14 @@ enum class OutputType { LEGACY, P2SH_SEGWIT, BECH32, + BECH32M, }; static constexpr auto OUTPUT_TYPES = std::array{ OutputType::LEGACY, OutputType::P2SH_SEGWIT, OutputType::BECH32, + OutputType::BECH32M, }; [[nodiscard]] bool ParseOutputType(const std::string& str, OutputType& output_type); @@ -45,4 +47,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key); */ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType); +/** Get the OutputType for a CTxDestination */ +std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest); + #endif // BITCOIN_OUTPUTTYPE_H diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 63897e0e05..4d4d3fb7d0 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2391,7 +2391,7 @@ static RPCHelpMan scantxoutset() LOCK(cs_main); CChainState& active_chainstate = chainman.ActiveChainstate(); active_chainstate.ForceFlushStateToDisk(); - pcursor = std::unique_ptr<CCoinsViewCursor>(active_chainstate.CoinsDB().Cursor()); + pcursor = active_chainstate.CoinsDB().Cursor(); CHECK_NONFATAL(pcursor); tip = active_chainstate.m_chain.Tip(); CHECK_NONFATAL(tip); @@ -2590,7 +2590,7 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } - pcursor = std::unique_ptr<CCoinsViewCursor>(chainstate.CoinsDB().Cursor()); + pcursor = chainstate.CoinsDB().Cursor(); tip = chainstate.m_blockman.LookupBlockIndex(stats.hashBlock); CHECK_NONFATAL(tip); } diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index ab239fe79c..5178ce60e8 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -131,6 +131,9 @@ static RPCHelpMan createmultisig() if (!ParseOutputType(request.params[2].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str())); } + if (output_type == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses"); + } } // Construct using pay-to-script-hash: diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 84a8b06c5c..fdbd2d7fc7 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -244,7 +244,7 @@ class ConstPubkeyProvider final : public PubkeyProvider bool m_xonly; public: - ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey, bool xonly = false) : PubkeyProvider(exp_index), m_pubkey(pubkey), m_xonly(xonly) {} + ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey, bool xonly) : PubkeyProvider(exp_index), m_pubkey(pubkey), m_xonly(xonly) {} bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) override { key = m_pubkey; @@ -640,20 +640,6 @@ public: std::optional<OutputType> GetOutputType() const override { return std::nullopt; } }; -static std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) { - if (std::holds_alternative<PKHash>(dest) || - std::holds_alternative<ScriptHash>(dest)) { - return OutputType::LEGACY; - } - if (std::holds_alternative<WitnessV0KeyHash>(dest) || - std::holds_alternative<WitnessV0ScriptHash>(dest) || - std::holds_alternative<WitnessV1Taproot>(dest) || - std::holds_alternative<WitnessUnknown>(dest)) { - return OutputType::BECH32; - } - return std::nullopt; -} - /** A parsed addr(A) descriptor. */ class AddressDescriptor final : public DescriptorImpl { @@ -874,7 +860,7 @@ public: { assert(m_subdescriptor_args.size() == m_depths.size()); } - std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } + std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; } bool IsSingleType() const final { return true; } }; @@ -931,7 +917,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S CPubKey pubkey(data); if (pubkey.IsFullyValid()) { if (permit_uncompressed || pubkey.IsCompressed()) { - return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey); + return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, false); } else { error = "Uncompressed keys are not allowed"; return nullptr; @@ -952,7 +938,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S if (permit_uncompressed || key.IsCompressed()) { CPubKey pubkey = key.GetPubKey(); out.keys.emplace(pubkey.GetID(), key); - return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey); + return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, ctx == ParseScriptContext::P2TR); } else { error = "Uncompressed keys are not allowed"; return nullptr; @@ -1221,26 +1207,50 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider) { - std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey); + std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false); + KeyOriginInfo info; + if (provider.GetKeyOrigin(pubkey.GetID(), info)) { + return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + } + return key_provider; +} + +std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider) +{ + unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02}; + std::copy(xkey.begin(), xkey.end(), full_key + 1); + CPubKey pubkey(full_key); + std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true); KeyOriginInfo info; if (provider.GetKeyOrigin(pubkey.GetID(), info)) { return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + } else { + full_key[0] = 0x03; + pubkey = CPubKey(full_key); + if (provider.GetKeyOrigin(pubkey.GetID(), info)) { + return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider)); + } } return key_provider; } std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptContext ctx, const SigningProvider& provider) { + if (ctx == ParseScriptContext::P2TR && script.size() == 34 && script[0] == 32 && script[33] == OP_CHECKSIG) { + XOnlyPubKey key{Span<const unsigned char>{script.data() + 1, script.data() + 33}}; + return std::make_unique<PKDescriptor>(InferXOnlyPubkey(key, ctx, provider)); + } + std::vector<std::vector<unsigned char>> data; TxoutType txntype = Solver(script, data); - if (txntype == TxoutType::PUBKEY) { + if (txntype == TxoutType::PUBKEY && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH)) { CPubKey pubkey(data[0]); if (pubkey.IsValid()) { return std::make_unique<PKDescriptor>(InferPubkey(pubkey, ctx, provider)); } } - if (txntype == TxoutType::PUBKEYHASH) { + if (txntype == TxoutType::PUBKEYHASH && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH)) { uint160 hash(data[0]); CKeyID keyid(hash); CPubKey pubkey; @@ -1248,7 +1258,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo return std::make_unique<PKHDescriptor>(InferPubkey(pubkey, ctx, provider)); } } - if (txntype == TxoutType::WITNESS_V0_KEYHASH && ctx != ParseScriptContext::P2WSH) { + if (txntype == TxoutType::WITNESS_V0_KEYHASH && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH)) { uint160 hash(data[0]); CKeyID keyid(hash); CPubKey pubkey; @@ -1256,7 +1266,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo return std::make_unique<WPKHDescriptor>(InferPubkey(pubkey, ctx, provider)); } } - if (txntype == TxoutType::MULTISIG) { + if (txntype == TxoutType::MULTISIG && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH)) { std::vector<std::unique_ptr<PubkeyProvider>> providers; for (size_t i = 1; i + 1 < data.size(); ++i) { CPubKey pubkey(data[i]); @@ -1273,7 +1283,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo if (sub) return std::make_unique<SHDescriptor>(std::move(sub)); } } - if (txntype == TxoutType::WITNESS_V0_SCRIPTHASH && ctx != ParseScriptContext::P2WSH) { + if (txntype == TxoutType::WITNESS_V0_SCRIPTHASH && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH)) { CScriptID scriptid; CRIPEMD160().Write(data[0].data(), data[0].size()).Finalize(scriptid.begin()); CScript subscript; @@ -1282,6 +1292,40 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo if (sub) return std::make_unique<WSHDescriptor>(std::move(sub)); } } + if (txntype == TxoutType::WITNESS_V1_TAPROOT && ctx == ParseScriptContext::TOP) { + // Extract x-only pubkey from output. + XOnlyPubKey pubkey; + std::copy(data[0].begin(), data[0].end(), pubkey.begin()); + // Request spending data. + TaprootSpendData tap; + if (provider.GetTaprootSpendData(pubkey, tap)) { + // If found, convert it back to tree form. + auto tree = InferTaprootTree(tap, pubkey); + if (tree) { + // If that works, try to infer subdescriptors for all leaves. + bool ok = true; + std::vector<std::unique_ptr<DescriptorImpl>> subscripts; //!< list of script subexpressions + std::vector<int> depths; //!< depth in the tree of each subexpression (same length subscripts) + for (const auto& [depth, script, leaf_ver] : *tree) { + std::unique_ptr<DescriptorImpl> subdesc; + if (leaf_ver == TAPROOT_LEAF_TAPSCRIPT) { + subdesc = InferScript(script, ParseScriptContext::P2TR, provider); + } + if (!subdesc) { + ok = false; + break; + } else { + subscripts.push_back(std::move(subdesc)); + depths.push_back(depth); + } + } + if (ok) { + auto key = InferXOnlyPubkey(tap.internal_key, ParseScriptContext::P2TR, provider); + return std::make_unique<TRDescriptor>(std::move(key), std::move(subscripts), std::move(depths)); + } + } + } + } CTxDestination dest; if (ExtractDestination(script, dest)) { diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 2dd173ee20..8cebbc5d10 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1847,16 +1847,14 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS return true; } -static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256& tapleaf_hash) +uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script) +{ + return (CHashWriter(HASHER_TAPLEAF) << leaf_version << script).GetSHA256(); +} + +uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash) { const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; - //! The internal pubkey (x-only, so no Y coordinate parity). - const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))}; - //! The output pubkey (taken from the scriptPubKey). - const XOnlyPubKey q{uint256(program)}; - // Compute the tapleaf hash. - tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256(); - // Compute the Merkle root from the leaf and the provided path. uint256 k = tapleaf_hash; for (int i = 0; i < path_len; ++i) { CHashWriter ss_branch{HASHER_TAPBRANCH}; @@ -1868,8 +1866,21 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c } k = ss_branch.GetSHA256(); } + return k; +} + +static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const uint256& tapleaf_hash) +{ + assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE); + assert(program.size() >= uint256::size()); + //! The internal pubkey (x-only, so no Y coordinate parity). + const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))}; + //! The output pubkey (taken from the scriptPubKey). + const XOnlyPubKey q{uint256(program)}; + // Compute the Merkle root from the leaf and the provided path. + const uint256 merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash); // Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity. - return q.CheckTapTweak(p, k, control[0] & 1); + return q.CheckTapTweak(p, merkle_root, control[0] & 1); } static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh) @@ -1929,7 +1940,8 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) { return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE); } - if (!VerifyTaprootCommitment(control, program, exec_script, execdata.m_tapleaf_hash)) { + execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, exec_script); + if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash)) { return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); } execdata.m_tapleaf_hash_init = true; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index ced5c28bc1..034c937b99 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -317,6 +317,12 @@ public: } }; +/** Compute the BIP341 tapleaf hash from leaf version & script. */ +uint256 ComputeTapleafHash(uint8_t leaf_version, const CScript& script); +/** Compute the BIP341 taproot script tree Merkle root from control block and leaf hash. + * Requires control block to have valid length (33 + k*32, with k in {0,1,..,128}). */ +uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint256& tapleaf_hash); + bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* error = nullptr); bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr); bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 748f00dda5..0fa5e56eb6 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -520,3 +520,138 @@ TaprootSpendData TaprootBuilder::GetSpendData() const } return spd; } + +std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output) +{ + // Verify that the output matches the assumed Merkle root and internal key. + auto tweak = spenddata.internal_key.CreateTapTweak(spenddata.merkle_root.IsNull() ? nullptr : &spenddata.merkle_root); + if (!tweak || tweak->first != output) return std::nullopt; + // If the Merkle root is 0, the tree is empty, and we're done. + std::vector<std::tuple<int, CScript, int>> ret; + if (spenddata.merkle_root.IsNull()) return ret; + + /** Data structure to represent the nodes of the tree we're going to build. */ + struct TreeNode { + /** Hash of this node, if known; 0 otherwise. */ + uint256 hash; + /** The left and right subtrees (note that their order is irrelevant). */ + std::unique_ptr<TreeNode> sub[2]; + /** If this is known to be a leaf node, a pointer to the (script, leaf_ver) pair. + * nullptr otherwise. */ + const std::pair<CScript, int>* leaf = nullptr; + /** Whether or not this node has been explored (is known to be a leaf, or known to have children). */ + bool explored = false; + /** Whether or not this node is an inner node (unknown until explored = true). */ + bool inner; + /** Whether or not we have produced output for this subtree. */ + bool done = false; + }; + + // Build tree from the provided branches. + TreeNode root; + root.hash = spenddata.merkle_root; + for (const auto& [key, control_blocks] : spenddata.scripts) { + const auto& [script, leaf_ver] = key; + for (const auto& control : control_blocks) { + // Skip script records with nonsensical leaf version. + if (leaf_ver < 0 || leaf_ver >= 0x100 || leaf_ver & 1) continue; + // Skip script records with invalid control block sizes. + if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || + ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) continue; + // Skip script records that don't match the control block. + if ((control[0] & TAPROOT_LEAF_MASK) != leaf_ver) continue; + // Skip script records that don't match the provided Merkle root. + const uint256 leaf_hash = ComputeTapleafHash(leaf_ver, script); + const uint256 merkle_root = ComputeTaprootMerkleRoot(control, leaf_hash); + if (merkle_root != spenddata.merkle_root) continue; + + TreeNode* node = &root; + size_t levels = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; + for (size_t depth = 0; depth < levels; ++depth) { + // Can't descend into a node which we already know is a leaf. + if (node->explored && !node->inner) return std::nullopt; + + // Extract partner hash from Merkle branch in control block. + uint256 hash; + std::copy(control.begin() + TAPROOT_CONTROL_BASE_SIZE + (levels - 1 - depth) * TAPROOT_CONTROL_NODE_SIZE, + control.begin() + TAPROOT_CONTROL_BASE_SIZE + (levels - depth) * TAPROOT_CONTROL_NODE_SIZE, + hash.begin()); + + if (node->sub[0]) { + // Descend into the existing left or right branch. + bool desc = false; + for (int i = 0; i < 2; ++i) { + if (node->sub[i]->hash == hash || (node->sub[i]->hash.IsNull() && node->sub[1-i]->hash != hash)) { + node->sub[i]->hash = hash; + node = &*node->sub[1-i]; + desc = true; + break; + } + } + if (!desc) return std::nullopt; // This probably requires a hash collision to hit. + } else { + // We're in an unexplored node. Create subtrees and descend. + node->explored = true; + node->inner = true; + node->sub[0] = std::make_unique<TreeNode>(); + node->sub[1] = std::make_unique<TreeNode>(); + node->sub[1]->hash = hash; + node = &*node->sub[0]; + } + } + // Cannot turn a known inner node into a leaf. + if (node->sub[0]) return std::nullopt; + node->explored = true; + node->inner = false; + node->leaf = &key; + node->hash = leaf_hash; + } + } + + // Recursive processing to turn the tree into flattened output. Use an explicit stack here to avoid + // overflowing the call stack (the tree may be 128 levels deep). + std::vector<TreeNode*> stack{&root}; + while (!stack.empty()) { + TreeNode& node = *stack.back(); + if (!node.explored) { + // Unexplored node, which means the tree is incomplete. + return std::nullopt; + } else if (!node.inner) { + // Leaf node; produce output. + ret.emplace_back(stack.size() - 1, node.leaf->first, node.leaf->second); + node.done = true; + stack.pop_back(); + } else if (node.sub[0]->done && !node.sub[1]->done && !node.sub[1]->explored && !node.sub[1]->hash.IsNull() && + (CHashWriter{HASHER_TAPBRANCH} << node.sub[1]->hash << node.sub[1]->hash).GetSHA256() == node.hash) { + // Whenever there are nodes with two identical subtrees under it, we run into a problem: + // the control blocks for the leaves underneath those will be identical as well, and thus + // they will all be matched to the same path in the tree. The result is that at the location + // where the duplicate occurred, the left child will contain a normal tree that can be explored + // and processed, but the right one will remain unexplored. + // + // This situation can be detected, by encountering an inner node with unexplored right subtree + // with known hash, and H_TapBranch(hash, hash) is equal to the parent node (this node)'s hash. + // + // To deal with this, simply process the left tree a second time (set its done flag to false; + // noting that the done flag of its children have already been set to false after processing + // those). To avoid ending up in an infinite loop, set the done flag of the right (unexplored) + // subtree to true. + node.sub[0]->done = false; + node.sub[1]->done = true; + } else if (node.sub[0]->done && node.sub[1]->done) { + // An internal node which we're finished with. + node.sub[0]->done = false; + node.sub[1]->done = false; + node.done = true; + stack.pop_back(); + } else if (!node.sub[0]->done) { + // An internal node whose left branch hasn't been processed yet. Do so first. + stack.push_back(&*node.sub[0]); + } else if (!node.sub[1]->done) { + // An internal node whose right branch hasn't been processed yet. Do so first. + stack.push_back(&*node.sub[1]); + } + } + + return ret; +} diff --git a/src/script/standard.h b/src/script/standard.h index 285dd4c116..ac4e2f3276 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -327,4 +327,12 @@ public: TaprootSpendData GetSpendData() const; }; +/** Given a TaprootSpendData and the output key, reconstruct its script tree. + * + * If the output doesn't match the spenddata, or if the data in spenddata is incomplete, + * std::nullopt is returned. Otherwise, a vector of (depth, script, leaf_ver) tuples is + * returned, corresponding to a depth-first traversal of the script tree. + */ +std::optional<std::vector<std::tuple<int, CScript, int>>> InferTaprootTree(const TaprootSpendData& spenddata, const XOnlyPubKey& output); + #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index 42f19d16c6..f452696689 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -183,8 +183,8 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) } { - const CCoinsViewCursor* coins_view_cursor = backend_coins_view.Cursor(); - assert(coins_view_cursor == nullptr); + std::unique_ptr<CCoinsViewCursor> coins_view_cursor = backend_coins_view.Cursor(); + assert(!coins_view_cursor); (void)backend_coins_view.EstimateSize(); (void)backend_coins_view.GetBestBlock(); (void)backend_coins_view.GetHeadBlocks(); diff --git a/src/txdb.cpp b/src/txdb.cpp index 762f71feb1..4b76bee5ab 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -168,9 +168,34 @@ bool CBlockTreeDB::ReadLastBlockFile(int &nFile) { return Read(DB_LAST_BLOCK, nFile); } -CCoinsViewCursor *CCoinsViewDB::Cursor() const +/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */ +class CCoinsViewDBCursor: public CCoinsViewCursor { - CCoinsViewDBCursor *i = new CCoinsViewDBCursor(const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock()); +public: + // Prefer using CCoinsViewDB::Cursor() since we want to perform some + // cache warmup on instantiation. + CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn): + CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} + ~CCoinsViewDBCursor() {} + + bool GetKey(COutPoint &key) const override; + bool GetValue(Coin &coin) const override; + unsigned int GetValueSize() const override; + + bool Valid() const override; + void Next() override; + +private: + std::unique_ptr<CDBIterator> pcursor; + std::pair<char, COutPoint> keyTmp; + + friend class CCoinsViewDB; +}; + +std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const +{ + auto i = std::make_unique<CCoinsViewDBCursor>( + const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock()); /* It seems that there are no "const iterators" for LevelDB. Since we only need read operations on it, use a const-cast to get around that restriction. */ diff --git a/src/txdb.h b/src/txdb.h index 0cf7e2f1b8..845d80788f 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -60,7 +60,7 @@ public: uint256 GetBestBlock() const override; std::vector<uint256> GetHeadBlocks() const override; bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override; - CCoinsViewCursor *Cursor() const override; + std::unique_ptr<CCoinsViewCursor> Cursor() const override; //! Attempt to update from an older database format. Returns whether an error occurred. bool Upgrade(); @@ -70,28 +70,6 @@ public: void ResizeCache(size_t new_cache_size) EXCLUSIVE_LOCKS_REQUIRED(cs_main); }; -/** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */ -class CCoinsViewDBCursor: public CCoinsViewCursor -{ -public: - ~CCoinsViewDBCursor() {} - - bool GetKey(COutPoint &key) const override; - bool GetValue(Coin &coin) const override; - unsigned int GetValueSize() const override; - - bool Valid() const override; - void Next() override; - -private: - CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn): - CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {} - std::unique_ptr<CDBIterator> pcursor; - std::pair<char, COutPoint> keyTmp; - - friend class CCoinsViewDB; -}; - /** Access to the block database (blocks/index/) */ class CBlockTreeDB : public CDBWrapper { diff --git a/src/util/system.cpp b/src/util/system.cpp index 5ea0139275..ee7a76be29 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -620,14 +620,14 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV m_settings.forced_settings[SettingName(strArg)] = strValue; } -void ArgsManager::AddCommand(const std::string& cmd, const std::string& help, const OptionsCategory& cat) +void ArgsManager::AddCommand(const std::string& cmd, const std::string& help) { Assert(cmd.find('=') == std::string::npos); Assert(cmd.at(0) != '-'); LOCK(cs_args); m_accept_any_command = false; // latch to false - std::map<std::string, Arg>& arg_map = m_available_args[cat]; + std::map<std::string, Arg>& arg_map = m_available_args[OptionsCategory::COMMANDS]; auto ret = arg_map.emplace(cmd, Arg{"", help, ArgsManager::COMMAND}); Assert(ret.second); // Fail on duplicate commands } diff --git a/src/util/system.h b/src/util/system.h index ea9870a343..3547bad585 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -374,7 +374,7 @@ public: /** * Add subcommand */ - void AddCommand(const std::string& cmd, const std::string& help, const OptionsCategory& cat); + void AddCommand(const std::string& cmd, const std::string& help); /** * Add many hidden arguments diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 4e9ba83ead..35649ab02c 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -286,6 +286,9 @@ RPCHelpMan importaddress() if (fP2SH) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); } + if (OutputTypeFromDestination(dest) == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets"); + } pwallet->MarkDirty(); @@ -962,6 +965,9 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); } + if (OutputTypeFromDestination(dest) == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets"); + } script = GetScriptForDestination(dest); } else { if (!IsHex(output)) { @@ -1086,6 +1092,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID if (!parsed_desc) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } + if (parsed_desc->GetOutputType() == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m descriptors cannot be imported into legacy wallets"); + } have_solving_data = parsed_desc->IsSolvable(); const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 8b1b6c6d95..bc5d771b6e 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -269,6 +269,9 @@ static RPCHelpMan getnewaddress() if (!ParseOutputType(request.params[1].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); } + if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); + } } CTxDestination dest; @@ -313,6 +316,9 @@ static RPCHelpMan getrawchangeaddress() if (!ParseOutputType(request.params[0].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); } + if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); + } } CTxDestination dest; @@ -1004,6 +1010,9 @@ static RPCHelpMan addmultisigaddress() if (!ParseOutputType(request.params[3].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str())); } + if (output_type == OutputType::BECH32M) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m multisig addresses cannot be created with legacy wallets"); + } } // Construct using pay-to-script-hash: @@ -3848,13 +3857,18 @@ RPCHelpMan getaddressinfo() isminetype mine = pwallet->IsMine(dest); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); - bool solvable = provider && IsSolvable(*provider, scriptPubKey); - ret.pushKV("solvable", solvable); - - if (solvable) { - ret.pushKV("desc", InferDescriptor(scriptPubKey, *provider)->ToString()); + if (provider) { + auto inferred = InferDescriptor(scriptPubKey, *provider); + bool solvable = inferred->IsSolvable() || IsSolvable(*provider, scriptPubKey); + ret.pushKV("solvable", solvable); + if (solvable) { + ret.pushKV("desc", inferred->ToString()); + } + } else { + ret.pushKV("solvable", false); } + DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(pwallet->GetScriptPubKeyMan(scriptPubKey)); if (desc_spk_man) { std::string desc_str; diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index c8baa0665e..44c3912544 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -22,6 +22,12 @@ const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { + if (LEGACY_OUTPUT_TYPES.count(type) == 0) { + error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated; + return false; + } + assert(type != OutputType::BECH32M); + LOCK(cs_KeyStore); error.clear(); @@ -289,14 +295,22 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat return true; } -bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) +bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) { + if (LEGACY_OUTPUT_TYPES.count(type) == 0) { + error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated; + return false; + } + assert(type != OutputType::BECH32M); + LOCK(cs_KeyStore); if (!CanGetAddresses(internal)) { + error = _("Error: Keypool ran out, please call keypoolrefill first").translated; return false; } if (!ReserveKeyFromKeyPool(index, keypool, internal)) { + error = _("Error: Keypool ran out, please call keypoolrefill first").translated; return false; } address = GetDestinationForKey(keypool.vchPubKey, type); @@ -1294,6 +1308,7 @@ void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const void LegacyScriptPubKeyMan::KeepDestination(int64_t nIndex, const OutputType& type) { + assert(type != OutputType::BECH32M); // Remove from key pool WalletBatch batch(m_storage.GetDatabase()); batch.ErasePool(nIndex); @@ -1327,6 +1342,7 @@ void LegacyScriptPubKeyMan::ReturnDestination(int64_t nIndex, bool fInternal, co bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType type, bool internal) { + assert(type != OutputType::BECH32M); if (!CanGetAddresses(internal)) { return false; } @@ -1395,6 +1411,7 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType type) { + assert(type != OutputType::BECH32M); if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { CTxDestination witdest = WitnessV0KeyHash(key.GetID()); CScript witprog = GetScriptForDestination(witdest); @@ -1706,10 +1723,9 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle return true; } -bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) +bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) { LOCK(cs_desc_man); - std::string error; bool result = GetNewDestination(type, address, error); index = m_wallet_descriptor.next_index - 1; return result; @@ -1880,6 +1896,12 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type) { + if (addr_type == OutputType::BECH32M) { + // Don't allow setting up taproot descriptors yet + // TODO: Allow setting up taproot descriptors + return false; + } + LOCK(cs_desc_man); assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); @@ -1909,6 +1931,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ desc_prefix = "wpkh(" + xpub + "/84'"; break; } + case OutputType::BECH32M: assert(false); // TODO: Setup taproot descriptor } // no default case, so the compiler can warn about missing cases assert(!desc_prefix.empty()); diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 3c4603608c..b2ca354b0a 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -181,7 +181,7 @@ public: virtual bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) { return false; } virtual bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { return false; } - virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) { return false; } + virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) { return false; } virtual void KeepDestination(int64_t index, const OutputType& type) {} virtual void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) {} @@ -254,6 +254,13 @@ public: boost::signals2::signal<void ()> NotifyCanGetAddressesChanged; }; +/** OutputTypes supported by the LegacyScriptPubKeyMan */ +static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES { + OutputType::LEGACY, + OutputType::P2SH_SEGWIT, + OutputType::BECH32, +}; + class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider { private: @@ -357,7 +364,7 @@ public: bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override; bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override; - bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) override; + bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) override; void KeepDestination(int64_t index, const OutputType& type) override; void ReturnDestination(int64_t index, bool internal, const CTxDestination&) override; @@ -566,7 +573,7 @@ public: bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override; bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override; - bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) override; + bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) override; void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) override; // Tops up the descriptor cache and m_map_script_pub_keys. The cache is stored in the wallet file diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index c8ded4c51e..6a8df437ae 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -618,8 +618,9 @@ bool CWallet::CreateTransactionInternal( // Reserve a new key pair from key pool. If it fails, provide a dummy // destination in case we don't need change. CTxDestination dest; - if (!reservedest.GetReservedDestination(dest, true)) { - error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first."); + std::string dest_err; + if (!reservedest.GetReservedDestination(dest, true, dest_err)) { + error = strprintf(_("Transaction needs a change address, but we can't generate it. %s"), dest_err); } scriptChange = GetScriptForDestination(dest); // A valid destination implies a change script (and diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 256faf2b23..c2586b60b8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1909,7 +1909,13 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang int witnessversion = 0; std::vector<unsigned char> witnessprogram; if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) { - return OutputType::BECH32; + if (GetScriptPubKeyMan(OutputType::BECH32M, true)) { + return OutputType::BECH32M; + } else if (GetScriptPubKeyMan(OutputType::BECH32, true)) { + return OutputType::BECH32; + } else { + return m_default_address_type; + } } } @@ -2112,7 +2118,7 @@ bool CWallet::GetNewDestination(const OutputType type, const std::string label, spk_man->TopUp(); result = spk_man->GetNewDestination(type, dest, error); } else { - error = strprintf("Error: No %s addresses available.", FormatOutputType(type)); + error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated; } if (result) { SetAddressBook(dest, label, "receive"); @@ -2127,8 +2133,7 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des error.clear(); ReserveDestination reservedest(this, type); - if (!reservedest.GetReservedDestination(dest, true)) { - error = _("Error: Keypool ran out, please call keypoolrefill first").translated; + if (!reservedest.GetReservedDestination(dest, true, error)) { return false; } @@ -2175,10 +2180,11 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co return result; } -bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal) +bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, std::string& error) { m_spk_man = pwallet->GetScriptPubKeyMan(type, internal); if (!m_spk_man) { + error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated; return false; } @@ -2188,7 +2194,7 @@ bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool inter m_spk_man->TopUp(); CKeyPool keypool; - if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) { + if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool, error)) { return false; } fInternal = keypool.fInternal; @@ -3033,7 +3039,7 @@ void CWallet::SetupLegacyScriptPubKeyMan() } auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this)); - for (const auto& type : OUTPUT_TYPES) { + for (const auto& type : LEGACY_OUTPUT_TYPES) { m_internal_spk_managers[type] = spk_manager.get(); m_external_spk_managers[type] = spk_manager.get(); } @@ -3086,6 +3092,11 @@ void CWallet::SetupDescriptorScriptPubKeyMans() for (bool internal : {false, true}) { for (OutputType t : OUTPUT_TYPES) { + if (t == OutputType::BECH32M) { + // Skip taproot (bech32m) for now + // TODO: Setup taproot (bech32m) descriptors by default + continue; + } auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal)); if (IsCrypted()) { if (IsLocked()) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 66f39edb4d..b63938c5f1 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -181,7 +181,7 @@ public: } //! Reserve an address - bool GetReservedDestination(CTxDestination& pubkey, bool internal); + bool GetReservedDestination(CTxDestination& pubkey, bool internal, std::string& error); //! Return reserved address void ReturnDestination(); //! Keep the address. Do not return it's key to the keypool when this object goes out of scope |