diff options
Diffstat (limited to 'src')
43 files changed, 911 insertions, 248 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 6edd5e75b7..eac7b38e03 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -220,6 +220,7 @@ BITCOIN_CORE_H = \ util/system.h \ util/macros.h \ util/memory.h \ + util/message.h \ util/moneystr.h \ util/rbf.h \ util/settings.h \ @@ -517,6 +518,7 @@ libbitcoin_util_a_SOURCES = \ util/error.cpp \ util/fees.cpp \ util/system.cpp \ + util/message.cpp \ util/moneystr.cpp \ util/rbf.cpp \ util/settings.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index c76f30de8e..7b4a6ae9f2 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -22,6 +22,7 @@ FUZZ_TARGETS = \ test/fuzz/blocktransactions_deserialize \ test/fuzz/blocktransactionsrequest_deserialize \ test/fuzz/blockundo_deserialize \ + test/fuzz/bloom_filter \ test/fuzz/bloomfilter_deserialize \ test/fuzz/coins_deserialize \ test/fuzz/decode_tx \ @@ -50,6 +51,7 @@ FUZZ_TARGETS = \ test/fuzz/psbt_input_deserialize \ test/fuzz/psbt_output_deserialize \ test/fuzz/pub_key_deserialize \ + test/fuzz/rolling_bloom_filter \ test/fuzz/script \ test/fuzz/script_deserialize \ test/fuzz/script_flags \ @@ -96,7 +98,8 @@ BITCOIN_TEST_SUITE = \ FUZZ_SUITE = \ test/fuzz/fuzz.cpp \ test/fuzz/fuzz.h \ - test/fuzz/FuzzedDataProvider.h + test/fuzz/FuzzedDataProvider.h \ + test/fuzz/util.h FUZZ_SUITE_LD_COMMON = \ $(LIBBITCOIN_SERVER) \ @@ -209,7 +212,8 @@ BITCOIN_TESTS += \ wallet/test/wallet_crypto_tests.cpp \ wallet/test/coinselector_tests.cpp \ wallet/test/init_tests.cpp \ - wallet/test/ismine_tests.cpp + wallet/test/ismine_tests.cpp \ + wallet/test/scriptpubkeyman_tests.cpp BITCOIN_TEST_SUITE += \ wallet/test/wallet_test_fixture.cpp \ @@ -346,6 +350,12 @@ test_fuzz_blockundo_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) test_fuzz_blockundo_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_blockundo_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_bloom_filter_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_bloom_filter_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_bloom_filter_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_bloom_filter_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_bloom_filter_SOURCES = $(FUZZ_SUITE) test/fuzz/bloom_filter.cpp + test_fuzz_bloomfilter_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DBLOOMFILTER_DESERIALIZE=1 test_fuzz_bloomfilter_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_bloomfilter_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) @@ -514,6 +524,12 @@ test_fuzz_pub_key_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) test_fuzz_pub_key_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_pub_key_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp +test_fuzz_rolling_bloom_filter_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_rolling_bloom_filter_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_rolling_bloom_filter_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_rolling_bloom_filter_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_rolling_bloom_filter_SOURCES = $(FUZZ_SUITE) test/fuzz/rolling_bloom_filter.cpp + test_fuzz_script_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) test_fuzz_script_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_script_LDADD = $(FUZZ_SUITE_LD_COMMON) @@ -639,7 +655,7 @@ endif %.cpp.test: %.cpp @echo Running tests: `cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1` from $< - $(AM_V_at)$(TEST_BINARY) -l test_suite -t "`cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" > $<.log 2>&1 || (cat $<.log && false) + $(AM_V_at)$(TEST_BINARY) --catch_system_errors=no -l test_suite -t "`cat $< | grep -E "(BOOST_FIXTURE_TEST_SUITE\\(|BOOST_AUTO_TEST_SUITE\\()" | cut -d '(' -f 2 | cut -d ',' -f 1 | cut -d ')' -f 1`" > $<.log 2>&1 || (cat $<.log && false) %.json.h: %.json @$(MKDIR_P) $(@D) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 31592b0f0a..a9183ac970 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -135,6 +135,7 @@ public: fDefaultConsistencyChecks = false; fRequireStandard = true; m_is_test_chain = false; + m_is_mockable_chain = false; checkpointData = { { @@ -231,7 +232,7 @@ public: fDefaultConsistencyChecks = false; fRequireStandard = false; m_is_test_chain = true; - + m_is_mockable_chain = false; checkpointData = { { @@ -303,6 +304,7 @@ public: fDefaultConsistencyChecks = true; fRequireStandard = true; m_is_test_chain = true; + m_is_mockable_chain = true; checkpointData = { { diff --git a/src/chainparams.h b/src/chainparams.h index 63398e587e..379c75e4be 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -68,6 +68,8 @@ public: bool RequireStandard() const { return fRequireStandard; } /** If this chain is exclusively used for testing */ bool IsTestChain() const { return m_is_test_chain; } + /** If this chain allows time to be mocked */ + bool IsMockableChain() const { return m_is_mockable_chain; } uint64_t PruneAfterHeight() const { return nPruneAfterHeight; } /** Minimum free space (in GB) needed for data directory */ uint64_t AssumedBlockchainSize() const { return m_assumed_blockchain_size; } @@ -102,6 +104,7 @@ protected: bool fDefaultConsistencyChecks; bool fRequireStandard; bool m_is_test_chain; + bool m_is_mockable_chain; CCheckpointData checkpointData; ChainTxData chainTxData; }; diff --git a/src/init.cpp b/src/init.cpp index 90d2624c7f..ece6214aa8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -157,7 +157,6 @@ NODISCARD static bool CreatePidFile() static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; static boost::thread_group threadGroup; -static CScheduler scheduler; void Interrupt(NodeContext& node) { @@ -295,6 +294,7 @@ void Shutdown(NodeContext& node) globalVerifyHandle.reset(); ECC_Stop(); if (node.mempool) node.mempool = nullptr; + node.scheduler.reset(); LogPrintf("%s: done\n", __func__); } @@ -450,7 +450,7 @@ void SetupServerArgs() gArgs.AddArg("-whitebind=<[permissions@]addr>", "Bind to given address and whitelist peers connecting to it. " "Use [host]:port notation for IPv6. Allowed permissions are bloomfilter (allow requesting BIP37 filtered blocks and transactions), " "noban (do not ban for misbehavior), " - "forcerelay (relay even non-standard transactions), " + "forcerelay (relay transactions that are already in the mempool; implies relay), " "relay (relay even in -blocksonly mode), " "and mempool (allow requesting BIP35 mempool contents). " "Specify multiple permissions separated by commas (default: noban,mempool,relay). Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -527,7 +527,7 @@ void SetupServerArgs() gArgs.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); gArgs.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted inbound peers with default permissions. This will relay transactions even if the transactions were already in the mempool or violate local relay policy. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted inbound peers with default permissions. This will relay transactions even if the transactions were already in the mempool. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); gArgs.AddArg("-whitelistrelay", strprintf("Add 'relay' permission to whitelisted inbound peers with default permissions. This will accept relayed transactions even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); @@ -1268,16 +1268,19 @@ bool AppInitMain(NodeContext& node) } } + assert(!node.scheduler); + node.scheduler = MakeUnique<CScheduler>(); + // Start the lightweight task scheduler thread - CScheduler::Function serviceLoop = std::bind(&CScheduler::serviceQueue, &scheduler); + CScheduler::Function serviceLoop = [&node]{ node.scheduler->serviceQueue(); }; threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop)); // Gather some entropy once per minute. - scheduler.scheduleEvery([]{ + node.scheduler->scheduleEvery([]{ RandAddPeriodic(); }, 60000); - GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); + GetMainSignals().RegisterBackgroundSignalScheduler(*node.scheduler); // Create client interfaces for wallets that are supposed to be loaded // according to -wallet and -disablewallet options. This only constructs @@ -1327,7 +1330,7 @@ bool AppInitMain(NodeContext& node) assert(!node.connman); node.connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()))); - node.peer_logic.reset(new PeerLogicValidation(node.connman.get(), node.banman.get(), scheduler)); + node.peer_logic.reset(new PeerLogicValidation(node.connman.get(), node.banman.get(), *node.scheduler)); RegisterValidationInterface(node.peer_logic.get()); // sanitize comments per BIP-0014, format user agent and check total size @@ -1819,7 +1822,7 @@ bool AppInitMain(NodeContext& node) connOptions.m_specified_outgoing = connect; } } - if (!node.connman->Start(scheduler, connOptions)) { + if (!node.connman->Start(*node.scheduler, connOptions)) { return false; } @@ -1848,11 +1851,11 @@ bool AppInitMain(NodeContext& node) uiInterface.InitMessage(_("Done loading").translated); for (const auto& client : node.chain_clients) { - client->start(scheduler); + client->start(*node.scheduler); } BanMan* banman = node.banman.get(); - scheduler.scheduleEvery([banman]{ + node.scheduler->scheduleEvery([banman]{ banman->DumpBanlist(); }, DUMP_BANS_INTERVAL * 1000); diff --git a/src/net_permissions.h b/src/net_permissions.h index a06d2f544d..ad74848347 100644 --- a/src/net_permissions.h +++ b/src/net_permissions.h @@ -15,7 +15,7 @@ enum NetPermissionFlags PF_BLOOMFILTER = (1U << 1), // Relay and accept transactions from this peer, even if -blocksonly is true PF_RELAY = (1U << 3), - // Always relay transactions from this peer, even if already in mempool or rejected from policy + // Always relay transactions from this peer, even if already in mempool // Keep parameter interaction: forcerelay implies relay PF_FORCERELAY = (1U << 2) | PF_RELAY, // Can't be banned for misbehavior @@ -59,4 +59,4 @@ public: CSubNet m_subnet; }; -#endif // BITCOIN_NET_PERMISSIONS_H
\ No newline at end of file +#endif // BITCOIN_NET_PERMISSIONS_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index cf4aee0647..1c1046b6ff 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -987,15 +987,6 @@ void Misbehaving(NodeId pnode, int howmuch, const std::string& message) EXCLUSIV } /** - * Returns true if the given validation state result may result in a peer - * banning/disconnecting us. We use this to determine which unaccepted - * transactions from a whitelisted peer that we can safely relay. - */ -static bool TxRelayMayResultInDisconnect(const TxValidationState& state) { - return state.GetResult() == TxValidationResult::TX_CONSENSUS; -} - -/** * Potentially ban a node based on the contents of a BlockValidationState object * * @param[in] via_compact_block this bool is passed in because net_processing should @@ -1064,10 +1055,9 @@ static bool MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& s * Potentially ban a node based on the contents of a TxValidationState object * * @return Returns true if the peer was punished (probably disconnected) - * - * Changes here may need to be reflected in TxRelayMayResultInDisconnect(). */ -static bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message = "") { +static bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, const std::string& message = "") +{ switch (state.GetResult()) { case TxValidationResult::TX_RESULT_UNSET: break; @@ -1095,11 +1085,6 @@ static bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state, } - - - - - ////////////////////////////////////////////////////////////////////////////// // // blockchain -> download logic notification @@ -2615,14 +2600,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (pfrom->HasPermission(PF_FORCERELAY)) { // Always relay transactions received from whitelisted peers, even - // if they were already in the mempool or rejected from it due - // to policy, allowing the node to function as a gateway for + // if they were already in the mempool, + // allowing the node to function as a gateway for // nodes hidden behind it. - // - // Never relay transactions that might result in being - // disconnected (or banned). - if (state.IsInvalid() && TxRelayMayResultInDisconnect(state)) { - LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state)); + if (!mempool.exists(tx.GetHash())) { + LogPrintf("Not relaying non-mempool transaction %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId()); } else { LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId()); RelayTransaction(tx.GetHash(), *connman); diff --git a/src/node/context.cpp b/src/node/context.cpp index 26a01420c8..5b19a41bd4 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -8,6 +8,7 @@ #include <interfaces/chain.h> #include <net.h> #include <net_processing.h> +#include <scheduler.h> NodeContext::NodeContext() {} NodeContext::~NodeContext() {} diff --git a/src/node/context.h b/src/node/context.h index dab5b5d048..1c592b456b 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -10,6 +10,7 @@ class BanMan; class CConnman; +class CScheduler; class CTxMemPool; class PeerLogicValidation; namespace interfaces { @@ -34,6 +35,7 @@ struct NodeContext { std::unique_ptr<BanMan> banman; std::unique_ptr<interfaces::Chain> chain; std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients; + std::unique_ptr<CScheduler> scheduler; //! Declare default constructor and destructor that are not inline, so code //! instantiating the NodeContext struct doesn't need to #include class diff --git a/src/outputtype.cpp b/src/outputtype.cpp index 567eecb5c9..71b5cba01c 100644 --- a/src/outputtype.cpp +++ b/src/outputtype.cpp @@ -82,30 +82,22 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, { // Add script to keystore keystore.AddCScript(script); - ScriptHash sh(script); // Note that scripts over 520 bytes are not yet supported. switch (type) { case OutputType::LEGACY: - keystore.AddCScript(GetScriptForDestination(sh)); - return sh; + return ScriptHash(script); case OutputType::P2SH_SEGWIT: case OutputType::BECH32: { CTxDestination witdest = WitnessV0ScriptHash(script); CScript witprog = GetScriptForDestination(witdest); // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key) - if (!IsSolvable(keystore, witprog)) { - // Since the wsh is invalid, add and return the sh instead. - keystore.AddCScript(GetScriptForDestination(sh)); - return sh; - } + if (!IsSolvable(keystore, witprog)) return ScriptHash(script); // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours. keystore.AddCScript(witprog); if (type == OutputType::BECH32) { return witdest; } else { - ScriptHash sh_w = ScriptHash(witprog); - keystore.AddCScript(GetScriptForDestination(sh_w)); - return sh_w; + return ScriptHash(witprog); } } default: assert(false); diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 5f2836cc75..883dcecf9a 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -11,7 +11,7 @@ #include <qt/walletmodel.h> #include <key_io.h> -#include <util/validation.h> // For strMessageMagic +#include <util/message.h> // For MessageSign(), MessageVerify() #include <wallet/wallet.h> #include <vector> @@ -141,13 +141,10 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() return; } - CHashWriter ss(SER_GETHASH, 0); - ss << strMessageMagic; - ss << ui->messageIn_SM->document()->toPlainText().toStdString(); + const std::string& message = ui->messageIn_SM->document()->toPlainText().toStdString(); + std::string signature; - std::vector<unsigned char> vchSig; - if (!key.SignCompact(ss.GetHash(), vchSig)) - { + if (!MessageSign(key, message, signature)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signing failed.") + QString("</nobr>")); return; @@ -156,7 +153,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() ui->statusLabel_SM->setStyleSheet("QLabel { color: green; }"); ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signed.") + QString("</nobr>")); - ui->signatureOut_SM->setText(QString::fromStdString(EncodeBase64(vchSig.data(), vchSig.size()))); + ui->signatureOut_SM->setText(QString::fromStdString(signature)); } void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked() @@ -189,51 +186,57 @@ void SignVerifyMessageDialog::on_addressBookButton_VM_clicked() void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked() { - CTxDestination destination = DecodeDestination(ui->addressIn_VM->text().toStdString()); - if (!IsValidDestination(destination)) { + const std::string& address = ui->addressIn_VM->text().toStdString(); + const std::string& signature = ui->signatureIn_VM->text().toStdString(); + const std::string& message = ui->messageIn_VM->document()->toPlainText().toStdString(); + + const auto result = MessageVerify(address, signature, message); + + if (result == MessageVerificationResult::OK) { + ui->statusLabel_VM->setStyleSheet("QLabel { color: green; }"); + } else { ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); - ui->statusLabel_VM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); - return; } - if (!boost::get<PKHash>(&destination)) { + + switch (result) { + case MessageVerificationResult::OK: + ui->statusLabel_VM->setText( + QString("<nobr>") + tr("Message verified.") + QString("</nobr>") + ); + return; + case MessageVerificationResult::ERR_INVALID_ADDRESS: + ui->statusLabel_VM->setText( + tr("The entered address is invalid.") + QString(" ") + + tr("Please check the address and try again.") + ); + return; + case MessageVerificationResult::ERR_ADDRESS_NO_KEY: ui->addressIn_VM->setValid(false); - ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); - ui->statusLabel_VM->setText(tr("The entered address does not refer to a key.") + QString(" ") + tr("Please check the address and try again.")); + ui->statusLabel_VM->setText( + tr("The entered address does not refer to a key.") + QString(" ") + + tr("Please check the address and try again.") + ); return; - } - - bool fInvalid = false; - std::vector<unsigned char> vchSig = DecodeBase64(ui->signatureIn_VM->text().toStdString().c_str(), &fInvalid); - - if (fInvalid) - { + case MessageVerificationResult::ERR_MALFORMED_SIGNATURE: ui->signatureIn_VM->setValid(false); - ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); - ui->statusLabel_VM->setText(tr("The signature could not be decoded.") + QString(" ") + tr("Please check the signature and try again.")); + ui->statusLabel_VM->setText( + tr("The signature could not be decoded.") + QString(" ") + + tr("Please check the signature and try again.") + ); return; - } - - CHashWriter ss(SER_GETHASH, 0); - ss << strMessageMagic; - ss << ui->messageIn_VM->document()->toPlainText().toStdString(); - - CPubKey pubkey; - if (!pubkey.RecoverCompact(ss.GetHash(), vchSig)) - { + case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED: ui->signatureIn_VM->setValid(false); - ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); - ui->statusLabel_VM->setText(tr("The signature did not match the message digest.") + QString(" ") + tr("Please check the signature and try again.")); + ui->statusLabel_VM->setText( + tr("The signature did not match the message digest.") + QString(" ") + + tr("Please check the signature and try again.") + ); return; - } - - if (!(CTxDestination(PKHash(pubkey)) == destination)) { - ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); - ui->statusLabel_VM->setText(QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>")); + case MessageVerificationResult::ERR_NOT_SIGNED: + ui->statusLabel_VM->setText( + QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>") + ); return; } - - ui->statusLabel_VM->setStyleSheet("QLabel { color: green; }"); - ui->statusLabel_VM->setText(QString("<nobr>") + tr("Message verified.") + QString("</nobr>")); } void SignVerifyMessageDialog::on_clearButton_VM_clicked() diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 2eaa3427eb..c1762483e9 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -27,6 +27,7 @@ public: static const CRPCConvertParam vRPCConvertParams[] = { { "setmocktime", 0, "timestamp" }, + { "mockscheduler", 0, "delta_time" }, { "utxoupdatepsbt", 1, "descriptors" }, { "generatetoaddress", 0, "nblocks" }, { "generatetoaddress", 2, "maxtries" }, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 74faf57011..4279756f4d 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -5,15 +5,17 @@ #include <httpserver.h> #include <key_io.h> +#include <node/context.h> #include <outputtype.h> #include <rpc/blockchain.h> #include <rpc/server.h> #include <rpc/util.h> +#include <scheduler.h> #include <script/descriptor.h> #include <util/check.h> +#include <util/message.h> // For MessageSign(), MessageVerify() #include <util/strencodings.h> #include <util/system.h> -#include <util/validation.h> #include <stdint.h> #include <tuple> @@ -42,8 +44,8 @@ static UniValue validateaddress(const JSONRPCRequest& request) "}\n" }, RPCExamples{ - HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") - + HelpExampleRpc("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleCli("validateaddress", EXAMPLE_ADDRESS) + + HelpExampleRpc("validateaddress", EXAMPLE_ADDRESS) }, }.Check(request); @@ -276,31 +278,21 @@ static UniValue verifymessage(const JSONRPCRequest& request) std::string strSign = request.params[1].get_str(); std::string strMessage = request.params[2].get_str(); - CTxDestination destination = DecodeDestination(strAddress); - if (!IsValidDestination(destination)) { + switch (MessageVerify(strAddress, strSign, strMessage)) { + case MessageVerificationResult::ERR_INVALID_ADDRESS: throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); - } - - const PKHash *pkhash = boost::get<PKHash>(&destination); - if (!pkhash) { + case MessageVerificationResult::ERR_ADDRESS_NO_KEY: throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); - } - - bool fInvalid = false; - std::vector<unsigned char> vchSig = DecodeBase64(strSign.c_str(), &fInvalid); - - if (fInvalid) + case MessageVerificationResult::ERR_MALFORMED_SIGNATURE: throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); - - CHashWriter ss(SER_GETHASH, 0); - ss << strMessageMagic; - ss << strMessage; - - CPubKey pubkey; - if (!pubkey.RecoverCompact(ss.GetHash(), vchSig)) + case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED: + case MessageVerificationResult::ERR_NOT_SIGNED: return false; + case MessageVerificationResult::OK: + return true; + } - return (pubkey.GetID() == *pkhash); + return false; } static UniValue signmessagewithprivkey(const JSONRPCRequest& request) @@ -332,15 +324,13 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); } - CHashWriter ss(SER_GETHASH, 0); - ss << strMessageMagic; - ss << strMessage; + std::string signature; - std::vector<unsigned char> vchSig; - if (!key.SignCompact(ss.GetHash(), vchSig)) + if (!MessageSign(key, strMessage, signature)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); + } - return EncodeBase64(vchSig.data(), vchSig.size()); + return signature; } static UniValue setmocktime(const JSONRPCRequest& request) @@ -371,6 +361,36 @@ static UniValue setmocktime(const JSONRPCRequest& request) return NullUniValue; } +static UniValue mockscheduler(const JSONRPCRequest& request) +{ + RPCHelpMan{"mockscheduler", + "\nBump the scheduler into the future (-regtest only)\n", + { + {"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." }, + }, + RPCResults{}, + RPCExamples{""}, + }.Check(request); + + if (!Params().IsMockableChain()) { + throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only"); + } + + // check params are valid values + RPCTypeCheck(request.params, {UniValue::VNUM}); + int64_t delta_seconds = request.params[0].get_int64(); + if ((delta_seconds <= 0) || (delta_seconds > 3600)) { + throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); + } + + // protect against null pointer dereference + CHECK_NONFATAL(g_rpc_node); + CHECK_NONFATAL(g_rpc_node->scheduler); + g_rpc_node->scheduler->MockForward(boost::chrono::seconds(delta_seconds)); + + return NullUniValue; +} + static UniValue RPCLockedMemoryInfo() { LockedPool::Stats stats = LockedPoolManager::Instance().stats(); @@ -575,6 +595,7 @@ static const CRPCCommand commands[] = /* Not shown in help */ { "hidden", "setmocktime", &setmocktime, {"timestamp"}}, + { "hidden", "mockscheduler", &mockscheduler, {"delta_time"}}, { "hidden", "echo", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, { "hidden", "echojson", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, }; diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 78586c22f9..7cefb2db96 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -14,6 +14,7 @@ #include <tuple> const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; +const std::string EXAMPLE_ADDRESS = "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\""; void RPCTypeCheck(const UniValue& params, const std::list<UniValueType>& typesExpected, diff --git a/src/rpc/util.h b/src/rpc/util.h index 065a992a88..f5edf69713 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -28,6 +28,12 @@ */ extern const std::string UNIX_EPOCH_TIME; +/** + * Example bech32 address used in multiple RPCExamples. The address is intentionally + * invalid to prevent accidental transactions by users. + */ +extern const std::string EXAMPLE_ADDRESS; + class FillableSigningProvider; class CPubKey; class CScript; diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 927a3f3820..72cca89d99 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -114,6 +114,28 @@ void CScheduler::scheduleFromNow(CScheduler::Function f, int64_t deltaMilliSecon schedule(f, boost::chrono::system_clock::now() + boost::chrono::milliseconds(deltaMilliSeconds)); } +void CScheduler::MockForward(boost::chrono::seconds delta_seconds) +{ + assert(delta_seconds.count() > 0 && delta_seconds < boost::chrono::hours{1}); + + { + boost::unique_lock<boost::mutex> lock(newTaskMutex); + + // use temp_queue to maintain updated schedule + std::multimap<boost::chrono::system_clock::time_point, Function> temp_queue; + + for (const auto& element : taskQueue) { + temp_queue.emplace_hint(temp_queue.cend(), element.first - delta_seconds, element.second); + } + + // point taskQueue to temp_queue + taskQueue = std::move(temp_queue); + } + + // notify that the taskQueue needs to be processed + newTaskScheduled.notify_one(); +} + static void Repeat(CScheduler* s, CScheduler::Function f, int64_t deltaMilliSeconds) { f(); diff --git a/src/scheduler.h b/src/scheduler.h index 7080adf34c..d18be0ea5e 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -55,6 +55,13 @@ public: // need more accurate scheduling, don't use this method. void scheduleEvery(Function f, int64_t deltaMilliSeconds); + /** + * Mock the scheduler to fast forward in time. + * Iterates through items on taskQueue and reschedules them + * to be delta_seconds sooner. + */ + void MockForward(boost::chrono::seconds delta_seconds); + // To keep things as simple as possible, there is no unschedule. // Services the queue 'forever'. Should be run in a thread, diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 773d6a55c4..b1d9a5bda7 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -339,14 +339,15 @@ class DescriptorImpl : public Descriptor { //! Public key arguments for this descriptor (size 1 for PK, PKH, WPKH; any size for Multisig). const std::vector<std::unique_ptr<PubkeyProvider>> m_pubkey_args; + //! The string name of the descriptor function. + const std::string m_name; + +protected: //! The sub-descriptor argument (nullptr for everything but SH and WSH). //! In doc/descriptors.m this is referred to as SCRIPT expressions sh(SCRIPT) //! and wsh(SCRIPT), and distinct from KEY expressions and ADDR expressions. const std::unique_ptr<DescriptorImpl> m_subdescriptor_arg; - //! The string name of the descriptor function. - const std::string m_name; -protected: //! Return a serialization of anything except pubkey and script arguments, to be prepended to those. virtual std::string ToStringExtra() const { return ""; } @@ -364,7 +365,7 @@ protected: virtual std::vector<CScript> MakeScripts(const std::vector<CPubKey>& pubkeys, const CScript* script, FlatSigningProvider& out) const = 0; public: - DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_subdescriptor_arg(std::move(script)), m_name(name) {} + DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_arg(std::move(script)) {} bool IsSolvable() const override { @@ -500,6 +501,8 @@ public: out = Merge(out, subprovider); } } + + Optional<OutputType> GetOutputType() const override { return nullopt; } }; /** A parsed addr(A) descriptor. */ @@ -512,6 +515,19 @@ protected: public: AddressDescriptor(CTxDestination destination) : DescriptorImpl({}, {}, "addr"), m_destination(std::move(destination)) {} bool IsSolvable() const final { return false; } + + Optional<OutputType> GetOutputType() const override + { + switch (m_destination.which()) { + case 1 /* PKHash */: + case 2 /* ScriptHash */: return OutputType::LEGACY; + case 3 /* WitnessV0ScriptHash */: + case 4 /* WitnessV0KeyHash */: + case 5 /* WitnessUnknown */: return OutputType::BECH32; + case 0 /* CNoDestination */: + default: return nullopt; + } + } }; /** A parsed raw(H) descriptor. */ @@ -524,6 +540,21 @@ protected: public: RawDescriptor(CScript script) : DescriptorImpl({}, {}, "raw"), m_script(std::move(script)) {} bool IsSolvable() const final { return false; } + + Optional<OutputType> GetOutputType() const override + { + CTxDestination dest; + ExtractDestination(m_script, dest); + switch (dest.which()) { + case 1 /* PKHash */: + case 2 /* ScriptHash */: return OutputType::LEGACY; + case 3 /* WitnessV0ScriptHash */: + case 4 /* WitnessV0KeyHash */: + case 5 /* WitnessUnknown */: return OutputType::BECH32; + case 0 /* CNoDestination */: + default: return nullopt; + } + } }; /** A parsed pk(P) descriptor. */ @@ -547,6 +578,7 @@ protected: } public: PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "pkh") {} + Optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; } }; /** A parsed wpkh(P) descriptor. */ @@ -561,6 +593,7 @@ protected: } public: WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "wpkh") {} + Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } }; /** A parsed combo(P) descriptor. */ @@ -612,6 +645,13 @@ protected: std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript* script, FlatSigningProvider&) const override { return Vector(GetScriptForDestination(ScriptHash(*script))); } public: SHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "sh") {} + + Optional<OutputType> GetOutputType() const override + { + assert(m_subdescriptor_arg); + if (m_subdescriptor_arg->GetOutputType() == OutputType::BECH32) return OutputType::P2SH_SEGWIT; + return OutputType::LEGACY; + } }; /** A parsed wsh(...) descriptor. */ @@ -621,6 +661,7 @@ protected: std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript* script, FlatSigningProvider&) const override { return Vector(GetScriptForDestination(WitnessV0ScriptHash(*script))); } public: WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {} + Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; } }; //////////////////////////////////////////////////////////////////////////// diff --git a/src/script/descriptor.h b/src/script/descriptor.h index a5a41d78dd..58b920c681 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_SCRIPT_DESCRIPTOR_H #define BITCOIN_SCRIPT_DESCRIPTOR_H +#include <optional.h> +#include <outputtype.h> #include <script/script.h> #include <script/sign.h> #include <script/signingprovider.h> @@ -71,6 +73,9 @@ struct Descriptor { * @param[out] out Any private keys available for the specified `pos`. */ virtual void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const = 0; + + /** @return The OutputType of the scriptPubKey(s) produced by this descriptor. Or nullopt if indeterminate (multiple or none) */ + virtual Optional<OutputType> GetOutputType() const = 0; }; /** Parse a `descriptor` string. Included private keys are put in `out`. diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index 6ad20480a7..76f31d2f6f 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -66,7 +66,53 @@ protected: using KeyMap = std::map<CKeyID, CKey>; using ScriptMap = std::map<CScriptID, CScript>; + /** + * Map of key id to unencrypted private keys known by the signing provider. + * Map may be empty if the provider has another source of keys, like an + * encrypted store. + */ KeyMap mapKeys GUARDED_BY(cs_KeyStore); + + /** + * Map of script id to scripts known by the signing provider. + * + * This map originally just held P2SH redeemScripts, and was used by wallet + * code to look up script ids referenced in "OP_HASH160 <script id> + * OP_EQUAL" P2SH outputs. Later in 605e8473a7d it was extended to hold + * P2WSH witnessScripts as well, and used to look up nested scripts + * referenced in "OP_0 <script hash>" P2WSH outputs. Later in commits + * f4691ab3a9d and 248f3a76a82, it was extended once again to hold segwit + * "OP_0 <key or script hash>" scriptPubKeys, in order to give the wallet a + * way to distinguish between segwit outputs that it generated addresses for + * and wanted to receive payments from, and segwit outputs that it never + * generated addresses for, but it could spend just because of having keys. + * (Before segwit activation it was also important to not treat segwit + * outputs to arbitrary wallet keys as payments, because these could be + * spent by anyone without even needing to sign with the keys.) + * + * Some of the scripts stored in mapScripts are memory-only and + * intentionally not saved to disk. Specifically, scripts added by + * ImplicitlyLearnRelatedKeyScripts(pubkey) calls are not written to disk so + * future wallet code can have flexibility to be more selective about what + * transaction outputs it recognizes as payments, instead of having to treat + * all outputs spending to keys it knows as payments. By contrast, + * mapScripts entries added by AddCScript(script), + * LearnRelatedScripts(pubkey, type), and LearnAllRelatedScripts(pubkey) + * calls are saved because they are all intentionally used to receive + * payments. + * + * The FillableSigningProvider::mapScripts script map should not be confused + * with LegacyScriptPubKeyMan::setWatchOnly script set. The two collections + * can hold the same scripts, but they serve different purposes. The + * setWatchOnly script set is intended to expand the set of outputs the + * wallet considers payments. Every output with a script it contains is + * considered to belong to the wallet, regardless of whether the script is + * solvable or signable. By contrast, the scripts in mapScripts are only + * used for solving, and to restrict which outputs are considered payments + * by the wallet. An output with a script in mapScripts, unlike + * setWatchOnly, is not automatically considered to belong to the wallet if + * it can't be solved and signed for. + */ ScriptMap mapScripts GUARDED_BY(cs_KeyStore); void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); diff --git a/src/test/bswap_tests.cpp b/src/test/bswap_tests.cpp index d5e2344a8b..0b4bfdb019 100644 --- a/src/test/bswap_tests.cpp +++ b/src/test/bswap_tests.cpp @@ -11,16 +11,16 @@ BOOST_FIXTURE_TEST_SUITE(bswap_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(bswap_tests) { - // Sibling in bitcoin/src/qt/test/compattests.cpp - uint16_t u1 = 0x1234; - uint32_t u2 = 0x56789abc; - uint64_t u3 = 0xdef0123456789abc; - uint16_t e1 = 0x3412; - uint32_t e2 = 0xbc9a7856; - uint64_t e3 = 0xbc9a78563412f0de; - BOOST_CHECK(bswap_16(u1) == e1); - BOOST_CHECK(bswap_32(u2) == e2); - BOOST_CHECK(bswap_64(u3) == e3); + // Sibling in bitcoin/src/qt/test/compattests.cpp + uint16_t u1 = 0x1234; + uint32_t u2 = 0x56789abc; + uint64_t u3 = 0xdef0123456789abc; + uint16_t e1 = 0x3412; + uint32_t e2 = 0xbc9a7856; + uint64_t e3 = 0xbc9a78563412f0de; + BOOST_CHECK(bswap_16(u1) == e1); + BOOST_CHECK(bswap_32(u2) == e2); + BOOST_CHECK(bswap_64(u3) == e3); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 2c2b3035e3..e5d51ab83b 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -78,7 +78,7 @@ BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup) BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) { auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, *m_node.scheduler); // Mock an outbound peer CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -148,7 +148,7 @@ static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerLogicValidat BOOST_AUTO_TEST_CASE(stale_tip_peer_management) { auto connman = MakeUnique<CConnmanTest>(0x1337, 0x1337); - auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, *m_node.scheduler); const Consensus::Params& consensusParams = Params().GetConsensus(); constexpr int max_outbound_full_relay = 8; @@ -221,7 +221,7 @@ BOOST_AUTO_TEST_CASE(DoS_banning) { auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler); banman->ClearBanned(); CAddress addr1(ip(0xa0b0c001), NODE_NONE); @@ -276,7 +276,7 @@ BOOST_AUTO_TEST_CASE(DoS_banscore) { auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler); banman->ClearBanned(); gArgs.ForceSetArg("-banscore", "111"); // because 11 is my favorite number @@ -323,7 +323,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) { auto banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); auto connman = MakeUnique<CConnman>(0x1337, 0x1337); - auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), scheduler); + auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), banman.get(), *m_node.scheduler); banman->ClearBanned(); int64_t nStartTime = GetTime(); diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index bcce8854e3..e99aee724e 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -62,7 +62,7 @@ std::string UseHInsteadOfApostrophe(const std::string& desc) const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}}; -void DoCheck(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY, +void DoCheck(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY, bool replace_apostrophe_with_h_in_prv=false, bool replace_apostrophe_with_h_in_pub=false) { FlatSigningProvider keys_priv, keys_pub; @@ -86,6 +86,10 @@ void DoCheck(const std::string& prv, const std::string& pub, int flags, const st BOOST_CHECK(parse_priv); BOOST_CHECK(parse_pub); + // Check that the correct OutputType is inferred + BOOST_CHECK(parse_priv->GetOutputType() == type); + BOOST_CHECK(parse_pub->GetOutputType() == type); + // Check private keys are extracted from the private version but not the public one. BOOST_CHECK(keys_priv.keys.size()); BOOST_CHECK(!keys_pub.keys.size()); @@ -181,29 +185,29 @@ void DoCheck(const std::string& prv, const std::string& pub, int flags, const st BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv); } -void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY) +void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const Optional<OutputType>& type, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY) { bool found_apostrophes_in_prv = false; bool found_apostrophes_in_pub = false; // Do not replace apostrophes with 'h' in prv and pub - DoCheck(prv, pub, flags, scripts, paths); + DoCheck(prv, pub, flags, scripts, type, paths); // Replace apostrophes with 'h' in prv but not in pub, if apostrophes are found in prv if (prv.find('\'') != std::string::npos) { found_apostrophes_in_prv = true; - DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false); + DoCheck(prv, pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false); } // Replace apostrophes with 'h' in pub but not in prv, if apostrophes are found in pub if (pub.find('\'') != std::string::npos) { found_apostrophes_in_pub = true; - DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true); + DoCheck(prv, pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true); } // Replace apostrophes with 'h' both in prv and in pub, if apostrophes are found in both if (found_apostrophes_in_prv && found_apostrophes_in_pub) { - DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true); + DoCheck(prv, pub, flags, scripts, type, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true); } } @@ -214,50 +218,50 @@ BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(descriptor_test) { // Basic single-key compressed - Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}); - Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}); - Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, {{1,0x80000002UL,3,0x80000004UL}}); - Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}); - Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}); + Check("combo(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac","00149a1c78a507689f6f54b847ad1cef1e614ee23f1e","a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, nullopt); + Check("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}}, nullopt); + Check("pkh([deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}}, OutputType::LEGACY, {{1,0x80000002UL,3,0x80000004UL}}); + Check("wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"00149a1c78a507689f6f54b847ad1cef1e614ee23f1e"}}, OutputType::BECH32); + Check("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a91484ab21b1b2fd065d4504ff693d832434b6108d7b87"}}, OutputType::P2SH_SEGWIT); CheckUnparsable("sh(wpkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY2))", "sh(wpkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5))", "Pubkey '03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5' is invalid"); // Invalid pubkey CheckUnparsable("pkh(deadbeef/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh(deadbeef/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Key origin start '[ character expected but not found, got 'd' instead"); // Missing start bracket in key origin CheckUnparsable("pkh([deadbeef]/1/2'/3/4']L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "pkh([deadbeef]/1/2'/3/4']03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Multiple ']' characters found for a single pubkey"); // Multiple end brackets in key origin // Basic single-key uncompressed - Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}); - Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}); - Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}); + Check("combo(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, nullopt); + Check("pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}}, nullopt); + Check("pkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}}, OutputType::LEGACY); CheckUnparsable("wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "Uncompressed keys are not allowed"); // No uncompressed keys in witness CheckUnparsable("wsh(pk(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "wsh(pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness CheckUnparsable("sh(wpkh(5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss))", "sh(wpkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235))", "Uncompressed keys are not allowed"); // No uncompressed keys in witness // Some unconventional single-key constructions - Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}); - Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}}); - Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}}); - Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}}); - Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}}); - Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}); + Check("sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}}, OutputType::LEGACY); + Check("sh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}}, OutputType::LEGACY); + Check("wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"00202e271faa2325c199d25d22e1ead982e45b64eeb4f31e73dbdf41bd4b5fec23fa"}}, OutputType::BECH32); + Check("wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"0020338e023079b91c58571b20e602d7805fb808c22473cbc391a41b1bd3a192e75b"}}, OutputType::BECH32); + Check("sh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a91472d0c5a3bfad8c3e7bd5303a72b94240e80b6f1787"}}, OutputType::P2SH_SEGWIT); + Check("sh(wsh(pkh(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(wsh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", SIGNABLE, {{"a914b61b92e2ca21bac1e72a3ab859a742982bea960a87"}}, OutputType::P2SH_SEGWIT); // Versions with BIP32 derivations - Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}); - Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, {{0}}); - Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, {{0xFFFFFFFFUL,0}}); - Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}}); - Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); - Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, {{0}, {1}}); + Check("combo([01234567]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([01234567]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac","001431a507b815593dfc51ffc7245ae7e5aee304246e","a9142aafb926eb247cb18240a7f4c07983ad1f37922687"}}, nullopt); + Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}}, nullopt, {{0}}); + Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}}, OutputType::LEGACY, {{0xFFFFFFFFUL,0}}); + Check("wpkh([ffffffff/13']xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*)", "wpkh([ffffffff/13']xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*)", RANGE, {{"0014326b2249e3a25d5dc60935f044ee835d090ba859"},{"0014af0bd98abc2f2cae66e36896a39ffe2d32984fb7"},{"00141fa798efd1cbf95cebf912c031b8a4a6e9fb9f27"}}, OutputType::BECH32, {{0x8000000DUL, 1, 2, 0}, {0x8000000DUL, 1, 2, 1}, {0x8000000DUL, 1, 2, 2}}); + Check("sh(wpkh(xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "sh(wpkh(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", RANGE | HARDENED, {{"a9149a4d9901d6af519b2a23d4a2f51650fcba87ce7b87"},{"a914bed59fc0024fae941d6e20a3b44a109ae740129287"},{"a9148483aa1116eb9c05c482a72bada4b1db24af654387"}}, OutputType::P2SH_SEGWIT, {{10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); + Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac","0014f90e3178ca25f2c808dc76624032d352fdbdfaf2","a91408f3ea8c68d4a7585bf9e8bda226723f70e445f087"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac","0014a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b7","a91473e39884cb71ae4e5ac9739e9225026c99763e6687"}}, nullopt, {{0}, {1}}); CheckUnparsable("combo([012345678]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo([012345678]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", "Fingerprint is not 4 bytes (9 characters instead of 8 characters)"); // Too long key fingerprint CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)", "Key path value 2147483648 is out of range"); // BIP 32 path element overflow CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/1aa)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1aa)", "Key path value '1aa' is not a valid uint32"); // Path is not valid uint // Multisig constructions - Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}); - Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}); - Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}); - Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}}); - Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}}); - Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); - Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}); + Check("multi(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt); + Check("sortedmulti(1,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "sortedmulti(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt); + Check("sortedmulti(1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss,L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "sortedmulti(1,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}}, nullopt); + Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}}); + Check("sortedmulti(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0/0/*)", "sortedmulti(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0/0/*)", RANGE, {{"5221025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b62102fbd47cc8034098f0e6a94c6aeee8528abf0a2153a5d8e46d325b7284c046784652ae"}, {"52210264fd4d1f5dea8ded94c61e9641309349b62f27fbffe807291f664e286bfbe6472103f4ece6dfccfa37b211eb3d0af4d0c61dba9ef698622dc17eecdf764beeb005a652ae"}, {"5221022ccabda84c30bad578b13c89eb3b9544ce149787e5b538175b1d1ba259cbb83321024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c52ae"}}, nullopt, {{0}, {1}, {2}, {0, 0, 0}, {0, 0, 1}, {0, 0, 2}}); + Check("wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", HARDENED | RANGE, {{"0020b92623201f3bb7c3771d45b2ad1d0351ea8fbf8cfe0a0e570264e1075fa1948f"},{"002036a08bbe4923af41cf4316817c93b8d37e2f635dd25cfff06bd50df6ae7ea203"},{"0020a96e7ab4607ca6b261bfe3245ffda9c746b28d3f59e83d34820ec0e2b36c139c"}}, OutputType::BECH32, {{0xFFFFFFFFUL,0}, {1,2,0}, {1,2,1}, {1,2,2}, {10, 20, 30, 40, 0x80000000UL}, {10, 20, 30, 40, 0x80000001UL}, {10, 20, 30, 40, 0x80000002UL}}); + Check("sh(wsh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9)))","sh(wsh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232)))", SIGNABLE, {{"a9147fc63e13dc25e8a95a3cee3d9a714ac3afd96f1e87"}}, OutputType::P2SH_SEGWIT); CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))", "P2SH script is too large, 547 bytes is larger than 520 bytes"); // P2SH does not fit 16 compressed pubkeys in a redeemscript CheckUnparsable("wsh(multi(2,[aaaaaaaa][aaaaaaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaaaaaaa][aaaaaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Multiple ']' characters found for a single pubkey"); // Double key origin descriptor CheckUnparsable("wsh(multi(2,[aaaagaaa]xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))", "wsh(multi(2,[aaagaaaa]xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/1/2/*,xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/10/20/30/40/*'))", "Fingerprint 'aaagaaaa' is not hex"); // Non hex fingerprint @@ -280,8 +284,8 @@ BOOST_AUTO_TEST_CASE(descriptor_test) CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Cannot have wsh within wsh"); // Cannot embed P2WSH inside P2WSH // Checksums - Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}}); - Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}}); + Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}}); + Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, {{0x8000006FUL,222},{0}}); CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#", "Expected 8 character checksum, not 0 characters"); // Empty checksum CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq", "Expected 8 character checksum, not 9 characters"); // Too long checksum CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5", "Expected 8 character checksum, not 7 characters"); // Too short checksum diff --git a/src/test/fuzz/bloom_filter.cpp b/src/test/fuzz/bloom_filter.cpp new file mode 100644 index 0000000000..b78744d9df --- /dev/null +++ b/src/test/fuzz/bloom_filter.cpp @@ -0,0 +1,80 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bloom.h> +#include <optional.h> +#include <primitives/transaction.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <uint256.h> + +#include <cassert> +#include <cstdint> +#include <string> +#include <vector> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + + CBloomFilter bloom_filter{ + fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, 10000000), + 1.0 / fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, std::numeric_limits<unsigned int>::max()), + fuzzed_data_provider.ConsumeIntegral<unsigned int>(), + static_cast<unsigned char>(fuzzed_data_provider.PickValueInArray({BLOOM_UPDATE_NONE, BLOOM_UPDATE_ALL, BLOOM_UPDATE_P2PUBKEY_ONLY, BLOOM_UPDATE_MASK}))}; + while (fuzzed_data_provider.remaining_bytes() > 0) { + switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 6)) { + case 0: { + const std::vector<unsigned char>& b = ConsumeRandomLengthByteVector(fuzzed_data_provider); + (void)bloom_filter.contains(b); + bloom_filter.insert(b); + const bool present = bloom_filter.contains(b); + assert(present); + break; + } + case 1: { + const Optional<COutPoint> out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); + if (!out_point) { + break; + } + (void)bloom_filter.contains(*out_point); + bloom_filter.insert(*out_point); + const bool present = bloom_filter.contains(*out_point); + assert(present); + break; + } + case 2: { + const Optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider); + if (!u256) { + break; + } + (void)bloom_filter.contains(*u256); + bloom_filter.insert(*u256); + const bool present = bloom_filter.contains(*u256); + assert(present); + break; + } + case 3: + bloom_filter.clear(); + break; + case 4: + bloom_filter.reset(fuzzed_data_provider.ConsumeIntegral<unsigned int>()); + break; + case 5: { + const Optional<CMutableTransaction> mut_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); + if (!mut_tx) { + break; + } + const CTransaction tx{*mut_tx}; + (void)bloom_filter.IsRelevantAndUpdate(tx); + break; + } + case 6: + bloom_filter.UpdateEmptyFull(); + break; + } + (void)bloom_filter.IsWithinSizeConstraints(); + } +} diff --git a/src/test/fuzz/rolling_bloom_filter.cpp b/src/test/fuzz/rolling_bloom_filter.cpp new file mode 100644 index 0000000000..ce69c4e8da --- /dev/null +++ b/src/test/fuzz/rolling_bloom_filter.cpp @@ -0,0 +1,50 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bloom.h> +#include <optional.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <uint256.h> + +#include <cassert> +#include <cstdint> +#include <string> +#include <vector> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + + CRollingBloomFilter rolling_bloom_filter{ + fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, 1000), + 0.999 / fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, std::numeric_limits<unsigned int>::max())}; + while (fuzzed_data_provider.remaining_bytes() > 0) { + switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 2)) { + case 0: { + const std::vector<unsigned char>& b = ConsumeRandomLengthByteVector(fuzzed_data_provider); + (void)rolling_bloom_filter.contains(b); + rolling_bloom_filter.insert(b); + const bool present = rolling_bloom_filter.contains(b); + assert(present); + break; + } + case 1: { + const Optional<uint256> u256 = ConsumeDeserializable<uint256>(fuzzed_data_provider); + if (!u256) { + break; + } + (void)rolling_bloom_filter.contains(*u256); + rolling_bloom_filter.insert(*u256); + const bool present = rolling_bloom_filter.contains(*u256); + assert(present); + break; + } + case 2: + rolling_bloom_filter.reset(); + break; + } + } +} diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h new file mode 100644 index 0000000000..62907c7e0b --- /dev/null +++ b/src/test/fuzz/util.h @@ -0,0 +1,39 @@ +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TEST_FUZZ_UTIL_H +#define BITCOIN_TEST_FUZZ_UTIL_H + +#include <attributes.h> +#include <optional.h> +#include <serialize.h> +#include <streams.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <version.h> + +#include <cstdint> +#include <string> +#include <vector> + +NODISCARD inline std::vector<uint8_t> ConsumeRandomLengthByteVector(FuzzedDataProvider& fuzzed_data_provider, size_t max_length = 4096) noexcept +{ + const std::string s = fuzzed_data_provider.ConsumeRandomLengthString(max_length); + return {s.begin(), s.end()}; +} + +template <typename T> +NODISCARD inline Optional<T> ConsumeDeserializable(FuzzedDataProvider& fuzzed_data_provider, size_t max_length = 4096) noexcept +{ + const std::vector<uint8_t>& buffer = ConsumeRandomLengthByteVector(fuzzed_data_provider, max_length); + CDataStream ds{buffer, SER_NETWORK, INIT_PROTO_VERSION}; + T obj; + try { + ds >> obj; + } catch (const std::ios_base::failure&) { + return nullopt; + } + return obj; +} + +#endif // BITCOIN_TEST_FUZZ_UTIL_H diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp index b292d5b0d0..a6cb34cf28 100644 --- a/src/test/scheduler_tests.cpp +++ b/src/test/scheduler_tests.cpp @@ -5,8 +5,6 @@ #include <random.h> #include <scheduler.h> -#include <test/util/setup_common.h> - #include <boost/thread.hpp> #include <boost/test/unit_test.hpp> @@ -155,4 +153,45 @@ BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered) BOOST_CHECK_EQUAL(counter2, 100); } +BOOST_AUTO_TEST_CASE(mockforward) +{ + CScheduler scheduler; + + int counter{0}; + CScheduler::Function dummy = [&counter]{counter++;}; + + // schedule jobs for 2, 5 & 8 minutes into the future + int64_t min_in_milli = 60*1000; + scheduler.scheduleFromNow(dummy, 2*min_in_milli); + scheduler.scheduleFromNow(dummy, 5*min_in_milli); + scheduler.scheduleFromNow(dummy, 8*min_in_milli); + + // check taskQueue + boost::chrono::system_clock::time_point first, last; + size_t num_tasks = scheduler.getQueueInfo(first, last); + BOOST_CHECK_EQUAL(num_tasks, 3ul); + + std::thread scheduler_thread([&]() { scheduler.serviceQueue(); }); + + // bump the scheduler forward 5 minutes + scheduler.MockForward(boost::chrono::seconds(5*60)); + + // ensure scheduler has chance to process all tasks queued for before 1 ms from now. + scheduler.scheduleFromNow([&scheduler]{ scheduler.stop(false); }, 1); + scheduler_thread.join(); + + // check that the queue only has one job remaining + num_tasks = scheduler.getQueueInfo(first, last); + BOOST_CHECK_EQUAL(num_tasks, 1ul); + + // check that the dummy function actually ran + BOOST_CHECK_EQUAL(counter, 2); + + // check that the time of the remaining job has been updated + boost::chrono::system_clock::time_point now = boost::chrono::system_clock::now(); + int delta = boost::chrono::duration_cast<boost::chrono::seconds>(first - now).count(); + // should be between 2 & 3 minutes from now + BOOST_CHECK(delta > 2*60 && delta < 3*60); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 0352d2d95a..bcf4033fe4 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -122,10 +122,9 @@ BOOST_AUTO_TEST_CASE(tx_valid) std::map<COutPoint, int64_t> mapprevOutValues; UniValue inputs = test[0].get_array(); bool fValid = true; - for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) { - const UniValue& input = inputs[inpIdx]; - if (!input.isArray()) - { + for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) { + const UniValue& input = inputs[inpIdx]; + if (!input.isArray()) { fValid = false; break; } @@ -209,10 +208,9 @@ BOOST_AUTO_TEST_CASE(tx_invalid) std::map<COutPoint, int64_t> mapprevOutValues; UniValue inputs = test[0].get_array(); bool fValid = true; - for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) { - const UniValue& input = inputs[inpIdx]; - if (!input.isArray()) - { + for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) { + const UniValue& input = inputs[inpIdx]; + if (!input.isArray()) { fValid = false; break; } diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index ccb3064d59..360377e58a 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -103,10 +103,12 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha g_rpc_node = &m_node; RegisterAllCoreRPCCommands(tableRPC); + m_node.scheduler = MakeUnique<CScheduler>(); + // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. - threadGroup.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); - GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); + threadGroup.create_thread([&]{ m_node.scheduler->serviceQueue(); }); + GetMainSignals().RegisterBackgroundSignalScheduler(*g_rpc_node->scheduler); pblocktree.reset(new CBlockTreeDB(1 << 20, true)); g_chainstate = MakeUnique<CChainState>(); @@ -147,6 +149,7 @@ TestingSetup::~TestingSetup() m_node.connman.reset(); m_node.banman.reset(); m_node.mempool = nullptr; + m_node.scheduler.reset(); UnloadBlockIndex(); g_chainstate.reset(); pblocktree.reset(); diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 6741be8480..56ad62eb24 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -85,7 +85,6 @@ private: struct TestingSetup : public BasicTestingSetup { NodeContext m_node; boost::thread_group threadGroup; - CScheduler scheduler; explicit TestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~TestingSetup(); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 42c2c50fa5..f86e713676 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -5,10 +5,14 @@ #include <util/system.h> #include <clientversion.h> +#include <hash.h> // For Hash() +#include <key.h> // For CKey #include <optional.h> #include <sync.h> #include <test/util/setup_common.h> #include <test/util/str.h> +#include <uint256.h> +#include <util/message.h> // For MessageSign(), MessageVerify(), MESSAGE_MAGIC #include <util/moneystr.h> #include <util/strencodings.h> #include <util/string.h> @@ -16,6 +20,7 @@ #include <util/spanparsing.h> #include <util/vector.h> +#include <array> #include <stdint.h> #include <thread> #include <univalue.h> @@ -2025,4 +2030,109 @@ BOOST_AUTO_TEST_CASE(test_tracked_vector) BOOST_CHECK_EQUAL(v8[2].copies, 0); } +BOOST_AUTO_TEST_CASE(message_sign) +{ + const std::array<unsigned char, 32> privkey_bytes = { + // just some random data + // derived address from this private key: 15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs + 0xD9, 0x7F, 0x51, 0x08, 0xF1, 0x1C, 0xDA, 0x6E, + 0xEE, 0xBA, 0xAA, 0x42, 0x0F, 0xEF, 0x07, 0x26, + 0xB1, 0xF8, 0x98, 0x06, 0x0B, 0x98, 0x48, 0x9F, + 0xA3, 0x09, 0x84, 0x63, 0xC0, 0x03, 0x28, 0x66 + }; + + const std::string message = "Trust no one"; + + const std::string expected_signature = + "IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk="; + + CKey privkey; + std::string generated_signature; + + BOOST_REQUIRE_MESSAGE(!privkey.IsValid(), + "Confirm the private key is invalid"); + + BOOST_CHECK_MESSAGE(!MessageSign(privkey, message, generated_signature), + "Sign with an invalid private key"); + + privkey.Set(privkey_bytes.begin(), privkey_bytes.end(), true); + + BOOST_REQUIRE_MESSAGE(privkey.IsValid(), + "Confirm the private key is valid"); + + BOOST_CHECK_MESSAGE(MessageSign(privkey, message, generated_signature), + "Sign with a valid private key"); + + BOOST_CHECK_EQUAL(expected_signature, generated_signature); +} + +BOOST_AUTO_TEST_CASE(message_verify) +{ + BOOST_CHECK_EQUAL( + MessageVerify( + "invalid address", + "signature should be irrelevant", + "message too"), + MessageVerificationResult::ERR_INVALID_ADDRESS); + + BOOST_CHECK_EQUAL( + MessageVerify( + "3B5fQsEXEaV8v6U3ejYc8XaKXAkyQj2MjV", + "signature should be irrelevant", + "message too"), + MessageVerificationResult::ERR_ADDRESS_NO_KEY); + + BOOST_CHECK_EQUAL( + MessageVerify( + "1KqbBpLy5FARmTPD4VZnDDpYjkUvkr82Pm", + "invalid signature, not in base64 encoding", + "message should be irrelevant"), + MessageVerificationResult::ERR_MALFORMED_SIGNATURE); + + BOOST_CHECK_EQUAL( + MessageVerify( + "1KqbBpLy5FARmTPD4VZnDDpYjkUvkr82Pm", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "message should be irrelevant"), + MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED); + + BOOST_CHECK_EQUAL( + MessageVerify( + "15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs", + "IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk=", + "I never signed this"), + MessageVerificationResult::ERR_NOT_SIGNED); + + BOOST_CHECK_EQUAL( + MessageVerify( + "15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs", + "IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk=", + "Trust no one"), + MessageVerificationResult::OK); + + BOOST_CHECK_EQUAL( + MessageVerify( + "11canuhp9X2NocwCq7xNrQYTmUgZAnLK3", + "IIcaIENoYW5jZWxsb3Igb24gYnJpbmsgb2Ygc2Vjb25kIGJhaWxvdXQgZm9yIGJhbmtzIAaHRtbCeDZINyavx14=", + "Trust me"), + MessageVerificationResult::OK); +} + +BOOST_AUTO_TEST_CASE(message_hash) +{ + const std::string unsigned_tx = "..."; + const std::string prefixed_message = + std::string(1, (char)MESSAGE_MAGIC.length()) + + MESSAGE_MAGIC + + std::string(1, (char)unsigned_tx.length()) + + unsigned_tx; + + const uint256 signature_hash = Hash(unsigned_tx.begin(), unsigned_tx.end()); + const uint256 message_hash1 = Hash(prefixed_message.begin(), prefixed_message.end()); + const uint256 message_hash2 = MessageHash(unsigned_tx); + + BOOST_CHECK_EQUAL(message_hash1, message_hash2); + BOOST_CHECK_NE(message_hash1, signature_hash); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp index ab8b957f7d..c24164528f 100644 --- a/src/test/validation_flush_tests.cpp +++ b/src/test/validation_flush_tests.cpp @@ -2,10 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // -#include <txmempool.h> -#include <validation.h> #include <sync.h> #include <test/util/setup_common.h> +#include <txmempool.h> +#include <validation.h> #include <boost/test/unit_test.hpp> @@ -85,12 +85,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) // This is contingent not only on the dynamic memory usage of the Coins // that we're adding (COIN_SIZE bytes per), but also on how much memory the // cacheCoins (unordered_map) preallocates. - // - // I came up with the count by examining the printed memory usage of the - // CCoinsCacheView, so it's sort of arbitrary - but it shouldn't change - // unless we somehow change the way the cacheCoins map allocates memory. - // - constexpr int COINS_UNTIL_CRITICAL = is_64_bit ? 4 : 5; + constexpr int COINS_UNTIL_CRITICAL{3}; for (int i{0}; i < COINS_UNTIL_CRITICAL; ++i) { COutPoint res = add_coin(view); @@ -101,17 +96,14 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) CoinsCacheSizeState::OK); } - // Adding an additional coin will push us over the edge to CRITICAL. - add_coin(view); - print_view_mem_usage(view); - - auto size_state = chainstate.GetCoinsCacheSizeState( - tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0); - - if (!is_64_bit && size_state == CoinsCacheSizeState::LARGE) { - // On 32 bit hosts, we may hit LARGE before CRITICAL. + // Adding some additional coins will push us over the edge to CRITICAL. + for (int i{0}; i < 4; ++i) { add_coin(view); print_view_mem_usage(view); + if (chainstate.GetCoinsCacheSizeState(tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0) == + CoinsCacheSizeState::CRITICAL) { + break; + } } BOOST_CHECK_EQUAL( diff --git a/src/util/message.cpp b/src/util/message.cpp new file mode 100644 index 0000000000..17603a43d2 --- /dev/null +++ b/src/util/message.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <hash.h> // For CHashWriter +#include <key.h> // For CKey +#include <key_io.h> // For DecodeDestination() +#include <pubkey.h> // For CPubKey +#include <script/standard.h> // For CTxDestination, IsValidDestination(), PKHash +#include <serialize.h> // For SER_GETHASH +#include <util/message.h> +#include <util/strencodings.h> // For DecodeBase64() + +#include <string> +#include <vector> + +/** + * Text used to signify that a signed message follows and to prevent + * inadvertently signing a transaction. + */ +const std::string MESSAGE_MAGIC = "Bitcoin Signed Message:\n"; + +MessageVerificationResult MessageVerify( + const std::string& address, + const std::string& signature, + const std::string& message) +{ + CTxDestination destination = DecodeDestination(address); + if (!IsValidDestination(destination)) { + return MessageVerificationResult::ERR_INVALID_ADDRESS; + } + + if (boost::get<PKHash>(&destination) == nullptr) { + return MessageVerificationResult::ERR_ADDRESS_NO_KEY; + } + + bool invalid = false; + std::vector<unsigned char> signature_bytes = DecodeBase64(signature.c_str(), &invalid); + if (invalid) { + return MessageVerificationResult::ERR_MALFORMED_SIGNATURE; + } + + CPubKey pubkey; + if (!pubkey.RecoverCompact(MessageHash(message), signature_bytes)) { + return MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED; + } + + if (!(CTxDestination(PKHash(pubkey)) == destination)) { + return MessageVerificationResult::ERR_NOT_SIGNED; + } + + return MessageVerificationResult::OK; +} + +bool MessageSign( + const CKey& privkey, + const std::string& message, + std::string& signature) +{ + std::vector<unsigned char> signature_bytes; + + if (!privkey.SignCompact(MessageHash(message), signature_bytes)) { + return false; + } + + signature = EncodeBase64(signature_bytes.data(), signature_bytes.size()); + + return true; +} + +uint256 MessageHash(const std::string& message) +{ + CHashWriter hasher(SER_GETHASH, 0); + hasher << MESSAGE_MAGIC << message; + + return hasher.GetHash(); +} diff --git a/src/util/message.h b/src/util/message.h new file mode 100644 index 0000000000..01fd14ce2d --- /dev/null +++ b/src/util/message.h @@ -0,0 +1,68 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_MESSAGE_H +#define BITCOIN_UTIL_MESSAGE_H + +#include <key.h> // For CKey +#include <uint256.h> + +#include <string> + +extern const std::string MESSAGE_MAGIC; + +/** The result of a signed message verification. + * Message verification takes as an input: + * - address (with whose private key the message is supposed to have been signed) + * - signature + * - message + */ +enum class MessageVerificationResult { + //! The provided address is invalid. + ERR_INVALID_ADDRESS, + + //! The provided address is valid but does not refer to a public key. + ERR_ADDRESS_NO_KEY, + + //! The provided signature couldn't be parsed (maybe invalid base64). + ERR_MALFORMED_SIGNATURE, + + //! A public key could not be recovered from the provided signature and message. + ERR_PUBKEY_NOT_RECOVERED, + + //! The message was not signed with the private key of the provided address. + ERR_NOT_SIGNED, + + //! The message verification was successful. + OK +}; + +/** Verify a signed message. + * @param[in] address Signer's bitcoin address, it must refer to a public key. + * @param[in] signature The signature in base64 format. + * @param[in] message The message that was signed. + * @return result code */ +MessageVerificationResult MessageVerify( + const std::string& address, + const std::string& signature, + const std::string& message); + +/** Sign a message. + * @param[in] privkey Private key to sign with. + * @param[in] message The message to sign. + * @param[out] signature Signature, base64 encoded, only set if true is returned. + * @return true if signing was successful. */ +bool MessageSign( + const CKey& privkey, + const std::string& message, + std::string& signature); + +/** + * Hashes a message for signing and verification in a manner that prevents + * inadvertently signing a transaction. + */ +uint256 MessageHash(const std::string& message); + +#endif // BITCOIN_UTIL_MESSAGE_H diff --git a/src/util/time.cpp b/src/util/time.cpp index 2afff2626b..f33966f149 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -94,10 +94,12 @@ std::string FormatISO8601DateTime(int64_t nTime) { struct tm ts; time_t time_val = nTime; #ifdef _MSC_VER - gmtime_s(&ts, &time_val); + if (gmtime_s(&ts, &time_val) != 0) { #else - gmtime_r(&time_val, &ts); + if (gmtime_r(&time_val, &ts) == nullptr) { #endif + return {}; + } return strprintf("%04i-%02i-%02iT%02i:%02i:%02iZ", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec); } @@ -105,10 +107,12 @@ std::string FormatISO8601Date(int64_t nTime) { struct tm ts; time_t time_val = nTime; #ifdef _MSC_VER - gmtime_s(&ts, &time_val); + if (gmtime_s(&ts, &time_val) != 0) { #else - gmtime_r(&time_val, &ts); + if (gmtime_r(&time_val, &ts) == nullptr) { #endif + return {}; + } return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday); } @@ -124,4 +128,4 @@ int64_t ParseISO8601DateTime(const std::string& str) if (ptime.is_not_a_date_time() || epoch > ptime) return 0; return (ptime - epoch).total_seconds(); -}
\ No newline at end of file +} diff --git a/src/util/validation.cpp b/src/util/validation.cpp index 89bc6665a4..ffbee21aeb 100644 --- a/src/util/validation.cpp +++ b/src/util/validation.cpp @@ -21,5 +21,3 @@ std::string FormatStateMessage(const ValidationState &state) return state.GetRejectReason(); } - -const std::string strMessageMagic = "Bitcoin Signed Message:\n"; diff --git a/src/util/validation.h b/src/util/validation.h index da2cf9f102..5ee260a055 100644 --- a/src/util/validation.h +++ b/src/util/validation.h @@ -13,6 +13,4 @@ class ValidationState; /** Convert ValidationState to a human-readable message for logging */ std::string FormatStateMessage(const ValidationState &state); -extern const std::string strMessageMagic; - #endif // BITCOIN_UTIL_VALIDATION_H diff --git a/src/wallet/psbtwallet.h b/src/wallet/psbtwallet.h index b35a0a58d1..8285bb901c 100644 --- a/src/wallet/psbtwallet.h +++ b/src/wallet/psbtwallet.h @@ -27,6 +27,6 @@ NODISCARD TransactionError FillPSBT(const CWallet* pwallet, bool& complete, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, - bool bip32derivs = false); + bool bip32derivs = true); #endif // BITCOIN_WALLET_PSBTWALLET_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index bc4ec77e31..0a9b739fb9 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -19,11 +19,11 @@ #include <script/sign.h> #include <util/bip32.h> #include <util/fees.h> +#include <util/message.h> // For MessageSign() #include <util/moneystr.h> #include <util/string.h> #include <util/system.h> #include <util/url.h> -#include <util/validation.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> #include <wallet/psbtwallet.h> @@ -576,15 +576,13 @@ static UniValue signmessage(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); } - CHashWriter ss(SER_GETHASH, 0); - ss << strMessageMagic; - ss << strMessage; + std::string signature; - std::vector<unsigned char> vchSig; - if (!key.SignCompact(ss.GetHash(), vchSig)) + if (!MessageSign(key, strMessage, signature)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); + } - return EncodeBase64(vchSig.data(), vchSig.size()); + return signature; } static UniValue getreceivedbyaddress(const JSONRPCRequest& request) @@ -2925,7 +2923,7 @@ static UniValue listunspent(const JSONRPCRequest& request) CTxDestination address; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, address); - bool reused = avoid_reuse && pwallet->IsUsedDestination(out.tx->GetHash(), out.i); + bool reused = avoid_reuse && pwallet->IsSpentKey(out.tx->GetHash(), out.i); if (destinations.size() && (!fValidAddress || !destinations.count(address))) continue; @@ -3724,8 +3722,6 @@ UniValue getaddressinfo(const JSONRPCRequest& request) return NullUniValue; } - const std::string example_address = "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\""; - RPCHelpMan{"getaddressinfo", "\nReturn information about the given bitcoin address.\n" "Some of the information will only be present if the address is in the active wallet.\n", @@ -3778,8 +3774,8 @@ UniValue getaddressinfo(const JSONRPCRequest& request) "}\n" }, RPCExamples{ - HelpExampleCli("getaddressinfo", example_address) + - HelpExampleRpc("getaddressinfo", example_address) + HelpExampleCli("getaddressinfo", EXAMPLE_ADDRESS) + + HelpExampleRpc("getaddressinfo", EXAMPLE_ADDRESS) }, }.Check(request); @@ -4070,7 +4066,7 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) " \"ALL|ANYONECANPAY\"\n" " \"NONE|ANYONECANPAY\"\n" " \"SINGLE|ANYONECANPAY\""}, - {"bip32derivs", RPCArg::Type::BOOL, /* default */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, + {"bip32derivs", RPCArg::Type::BOOL, /* default */ "true", "Include BIP 32 derivation paths for public keys if we know them"}, }, RPCResult{ "{ (json object)\n" @@ -4097,7 +4093,7 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) // Fill transaction with our data and also sign bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); - bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool(); + bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool(); bool complete = true; const TransactionError err = FillPSBT(pwallet, psbtx, complete, nHashType, sign, bip32derivs); if (err != TransactionError::OK) { @@ -4180,7 +4176,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) " \"CONSERVATIVE\""}, }, "options"}, - {"bip32derivs", RPCArg::Type::BOOL, /* default */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, + {"bip32derivs", RPCArg::Type::BOOL, /* default */ "true", "Include BIP 32 derivation paths for public keys if we know them"}, }, RPCResult{ "{\n" @@ -4219,7 +4215,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) PartiallySignedTransaction psbtx(rawTx); // Fill transaction with out data but don't sign - bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool(); + bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool(); bool complete = true; const TransactionError err = FillPSBT(pwallet, psbtx, complete, 1, false, bip32derivs); if (err != TransactionError::OK) { diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 4c9d88973e..0c95ab29b1 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -70,7 +70,15 @@ bool HaveKeys(const std::vector<valtype>& pubkeys, const LegacyScriptPubKeyMan& return true; } -IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion) +//! Recursively solve script and return spendable/watchonly/invalid status. +//! +//! @param keystore legacy key and script store +//! @param script script to solve +//! @param sigversion script type (top-level / redeemscript / witnessscript) +//! @param recurse_scripthash whether to recurse into nested p2sh and p2wsh +//! scripts or simply treat any script that has been +//! stored in the keystore as spendable +IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion, bool recurse_scripthash=true) { IsMineResult ret = IsMineResult::NO; @@ -129,7 +137,7 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s CScriptID scriptID = CScriptID(uint160(vSolutions[0])); CScript subscript; if (keystore.GetCScript(scriptID, subscript)) { - ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH)); + ret = std::max(ret, recurse_scripthash ? IsMineInner(keystore, subscript, IsMineSigVersion::P2SH) : IsMineResult::SPENDABLE); } break; } @@ -147,7 +155,7 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s CScriptID scriptID = CScriptID(hash); CScript subscript; if (keystore.GetCScript(scriptID, subscript)) { - ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0)); + ret = std::max(ret, recurse_scripthash ? IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0) : IsMineResult::SPENDABLE); } break; } @@ -476,11 +484,11 @@ std::unique_ptr<SigningProvider> LegacyScriptPubKeyMan::GetSigningProvider(const bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sigdata) { - if (IsMine(script) != ISMINE_NO) { - // If it IsMine, we can always provide in some way - return true; - } else if (HaveCScript(CScriptID(script))) { - // We can still provide some stuff if we have the script, but IsMine failed because we don't have keys + IsMineResult ismine = IsMineInner(*this, script, IsMineSigVersion::TOP, /* recurse_scripthash= */ false); + if (ismine == IsMineResult::SPENDABLE || ismine == IsMineResult::WATCH_ONLY) { + // If ismine, it means we recognize keys or script ids in the script, or + // are watching the script itself, and we can at least provide metadata + // or solving information, even if not able to sign fully. return true; } else { // If, given the stuff in sigdata, we could make a valid sigature, then we can provide for this script diff --git a/src/wallet/test/scriptpubkeyman_tests.cpp b/src/wallet/test/scriptpubkeyman_tests.cpp new file mode 100644 index 0000000000..757865ea37 --- /dev/null +++ b/src/wallet/test/scriptpubkeyman_tests.cpp @@ -0,0 +1,43 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <key.h> +#include <script/standard.h> +#include <test/util/setup_common.h> +#include <wallet/scriptpubkeyman.h> +#include <wallet/wallet.h> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(scriptpubkeyman_tests, BasicTestingSetup) + +// Test LegacyScriptPubKeyMan::CanProvide behavior, making sure it returns true +// for recognized scripts even when keys may not be available for signing. +BOOST_AUTO_TEST_CASE(CanProvide) +{ + // Set up wallet and keyman variables. + NodeContext node; + std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); + LegacyScriptPubKeyMan& keyman = *wallet.GetOrCreateLegacyScriptPubKeyMan(); + + // Make a 1 of 2 multisig script + std::vector<CKey> keys(2); + std::vector<CPubKey> pubkeys; + for (CKey& key : keys) { + key.MakeNewKey(true); + pubkeys.emplace_back(key.GetPubKey()); + } + CScript multisig_script = GetScriptForMultisig(1, pubkeys); + CScript p2sh_script = GetScriptForDestination(ScriptHash(multisig_script)); + SignatureData data; + + // Verify the p2sh(multisig) script is not recognized until the multisig + // script is added to the keystore to make it solvable + BOOST_CHECK(!keyman.CanProvide(p2sh_script, data)); + keyman.AddCScript(multisig_script); + BOOST_CHECK(keyman.CanProvide(p2sh_script, data)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6b9f53f7c5..02fe59b601 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -714,7 +714,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) return success; } -void CWallet::SetUsedDestinationState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) +void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) { AssertLockHeld(cs_wallet); const CWalletTx* srctx = GetWalletTx(hash); @@ -734,7 +734,7 @@ void CWallet::SetUsedDestinationState(WalletBatch& batch, const uint256& hash, u } } -bool CWallet::IsUsedDestination(const uint256& hash, unsigned int n) const +bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const { AssertLockHeld(cs_wallet); CTxDestination dst; @@ -777,7 +777,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) for (const CTxIn& txin : wtxIn.tx->vin) { const COutPoint& op = txin.prevout; - SetUsedDestinationState(batch, op.hash, op.n, true, tx_destinations); + SetSpentKeyState(batch, op.hash, op.n, true, tx_destinations); } MarkDestinationsDirty(tx_destinations); @@ -1878,7 +1878,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter uint256 hashTx = GetHash(); for (unsigned int i = 0; i < tx->vout.size(); i++) { - if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i))) { + if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) { const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, filter); if (!MoneyRange(nCredit)) @@ -2168,7 +2168,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< continue; } - if (!allow_used_addresses && IsUsedDestination(wtxid, i)) { + if (!allow_used_addresses && IsSpentKey(wtxid, i)) { continue; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a918bb8833..6c1c3040c2 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -817,8 +817,8 @@ public: bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // Whether this or any known UTXO with the same single key has been spent. - bool IsUsedDestination(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void SetUsedDestinationState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const; |