From 5935f0126ef37175a8fbfee017b470af13aad46d Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Wed, 8 May 2019 14:28:57 -0400 Subject: [PATCH 01/36] build with -fstack-reuse=none Github-Pull: #15983 Rebased-From: faf38bc056e523485520f98f3f725c583a3b89bf --- configure.ac | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/configure.ac b/configure.ac index da922b3cc0e..5335a6a4465 100644 --- a/configure.ac +++ b/configure.ac @@ -725,6 +725,10 @@ if test x$TARGET_OS != xwindows; then AX_CHECK_COMPILE_FLAG([-fPIC],[PIC_FLAGS="-fPIC"]) fi +# All versions of gcc that we commonly use for building are subject to bug +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90348. To work around that, set +# -fstack-reuse=none for all gcc builds. (Only gcc understands this flag) +AX_CHECK_COMPILE_FLAG([-fstack-reuse=none],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fstack-reuse=none"]) if test x$use_hardening != xno; then use_hardening=yes AX_CHECK_COMPILE_FLAG([-Wstack-protector],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -Wstack-protector"]) From 9c1a607a092039ada85aa7a8c12c6062a8e89b94 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Thu, 9 May 2019 09:16:29 -0400 Subject: [PATCH 02/36] net: Rename ::fRelayTxes to ::g_relay_txes This helps to distinguish it from CNode::fRelayTxes and avoid bugs like 425278d17bd0edf8a3a7cc81e55016f7fd8e7726 Github-Pull: #15990 Rebased-From: fa1dce7329d3e74d46ab98b93772b1832a3f1819 --- src/init.cpp | 2 +- src/net.cpp | 2 +- src/net.h | 2 +- src/net_processing.cpp | 6 +++--- src/rpc/net.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index d0933766a52..52f1f0d84f6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1411,7 +1411,7 @@ bool AppInitMain(InitInterfaces& interfaces) // see Step 2: parameter interactions for more information about these fListen = gArgs.GetBoolArg("-listen", DEFAULT_LISTEN); fDiscover = gArgs.GetBoolArg("-discover", true); - fRelayTxes = !gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY); + g_relay_txes = !gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY); for (const std::string& strAddr : gArgs.GetArgs("-externalip")) { CService addrLocal; diff --git a/src/net.cpp b/src/net.cpp index 67c6a5c7985..a3bca8d1131 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -79,7 +79,7 @@ static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // S // bool fDiscover = true; bool fListen = true; -bool fRelayTxes = true; +bool g_relay_txes = !DEFAULT_BLOCKSONLY; CCriticalSection cs_mapLocalHost; std::map mapLocalHost GUARDED_BY(cs_mapLocalHost); static bool vfLimited[NET_MAX] GUARDED_BY(cs_mapLocalHost) = {}; diff --git a/src/net.h b/src/net.h index db44ec63339..6cae35e8e09 100644 --- a/src/net.h +++ b/src/net.h @@ -523,7 +523,7 @@ CAddress GetLocalAddress(const CNetAddr *paddrPeer, ServiceFlags nLocalServices) extern bool fDiscover; extern bool fListen; -extern bool fRelayTxes; +extern bool g_relay_txes; extern limitedmap mapAlreadyAskedFor; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index d524d626c39..550be5f7ca7 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -336,7 +336,7 @@ static void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime) CAddress addrMe = CAddress(CService(), nLocalNodeServices); connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe, - nonce, strSubVersion, nNodeStartingHeight, ::fRelayTxes)); + nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes)); if (fLogIPs) { LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), nodeid); @@ -2011,7 +2011,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return false; } - bool fBlocksOnly = !fRelayTxes; + bool fBlocksOnly = !g_relay_txes; // Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)) @@ -2266,7 +2266,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (strCommand == NetMsgType::TX) { // Stop processing the transaction early if // We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off - if (!fRelayTxes && (!pfrom->fWhitelisted || !gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))) + if (!g_relay_txes && (!pfrom->fWhitelisted || !gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))) { LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId()); return true; diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index c7b3478f440..a5f590757d2 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -495,7 +495,7 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) obj.pushKV("protocolversion",PROTOCOL_VERSION); if(g_connman) obj.pushKV("localservices", strprintf("%016x", g_connman->GetLocalServices())); - obj.pushKV("localrelay", fRelayTxes); + obj.pushKV("localrelay", g_relay_txes); obj.pushKV("timeoffset", GetTimeOffset()); if (g_connman) { obj.pushKV("networkactive", g_connman->GetNetworkActive()); From 8f215c7a270066b315060a112fe69d968ab8b292 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Thu, 9 May 2019 10:36:53 -0400 Subject: [PATCH 03/36] test: Format predicate source as multiline on error Github-Pull: #15990 Rebased-From: fa3872e7b4540857261aed948b94b6b2bfdbc3d1 --- test/functional/test_framework/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index fef99824127..87e2dbaf163 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -219,7 +219,7 @@ def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=N time.sleep(0.05) # Print the cause of the timeout - predicate_source = inspect.getsourcelines(predicate) + predicate_source = "''''\n" + inspect.getsource(predicate) + "'''" logger.error("wait_until() failed. Predicate: {}".format(predicate_source)) if attempt >= attempts: raise AssertionError("Predicate {} not true after {} attempts".format(predicate_source, attempts)) From 3460555f4711fc9d2ac3d783e30fae00c052b179 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Thu, 9 May 2019 10:42:56 -0400 Subject: [PATCH 04/36] test: Add test for p2p_blocksonly Github-Pull: #15990 Rebased-From: fa320de79faaca2b088fcbe7f76701faa9bff236 --- test/functional/p2p_blocksonly.py | 58 ++++++++++++++++++++++ test/functional/test_framework/mininode.py | 8 +++ test/functional/test_runner.py | 1 + 3 files changed, 67 insertions(+) create mode 100755 test/functional/p2p_blocksonly.py diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py new file mode 100755 index 00000000000..12cb06a4079 --- /dev/null +++ b/test/functional/p2p_blocksonly.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# Copyright (c) 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. +"""Test p2p blocksonly""" + +from test_framework.messages import msg_tx, CTransaction, FromHex +from test_framework.mininode import P2PInterface +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +class P2PBlocksOnly(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + self.extra_args = [["-blocksonly"]] + + def run_test(self): + self.nodes[0].add_p2p_connection(P2PInterface()) + + self.log.info('Check that txs from p2p are rejected') + prevtx = self.nodes[0].getblock(self.nodes[0].getblockhash(1), 2)['tx'][0] + rawtx = self.nodes[0].createrawtransaction( + inputs=[{ + 'txid': prevtx['txid'], + 'vout': 0 + }], + outputs=[{ + self.nodes[0].get_deterministic_priv_key().address: 50 - 0.00125 + }], + ) + sigtx = self.nodes[0].signrawtransactionwithkey( + hexstring=rawtx, + privkeys=[self.nodes[0].get_deterministic_priv_key().key], + prevtxs=[{ + 'txid': prevtx['txid'], + 'vout': 0, + 'scriptPubKey': prevtx['vout'][0]['scriptPubKey']['hex'], + }], + )['hex'] + assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], False) + with self.nodes[0].assert_debug_log(['transaction sent in violation of protocol peer=0']): + self.nodes[0].p2p.send_message(msg_tx(FromHex(CTransaction(), sigtx))) + self.nodes[0].p2p.sync_with_ping() + assert_equal(self.nodes[0].getmempoolinfo()['size'], 0) + + self.log.info('Check that txs from rpc are not rejected and relayed to other peers') + assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True) + txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid'] + with self.nodes[0].assert_debug_log(['received getdata for: tx {} peer=0'.format(txid)]): + self.nodes[0].sendrawtransaction(sigtx) + self.nodes[0].p2p.wait_for_tx(txid) + assert_equal(self.nodes[0].getmempoolinfo()['size'], 1) + + +if __name__ == '__main__': + P2PBlocksOnly().main() diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index ac7cc068bdb..6afdd501762 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -364,6 +364,14 @@ class P2PInterface(P2PConnection): # Message receiving helper methods + def wait_for_tx(self, txid, timeout=60): + def test_function(): + if not self.last_message.get('tx'): + return False + return self.last_message['tx'].tx.rehash() == txid + + wait_until(test_function, timeout=timeout, lock=mininode_lock) + def wait_for_block(self, blockhash, timeout=60): test_function = lambda: self.last_message.get("block") and self.last_message["block"].block.rehash() == blockhash wait_until(test_function, timeout=timeout, lock=mininode_lock) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index bb9ac58e46c..06d3552195f 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -134,6 +134,7 @@ BASE_SCRIPTS = [ 'rpc_net.py', 'wallet_keypool.py', 'p2p_mempool.py', + 'p2p_blocksonly.py', 'mining_prioritisetransaction.py', 'p2p_invalid_locator.py', 'p2p_invalid_block.py', From 890a92eba819763a4dd182245cc7daddacec2ef4 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Thu, 9 May 2019 09:29:08 -0400 Subject: [PATCH 05/36] doc: Mention blocksonly in reduce-traffic.md, unhide option Github-Pull: #15990 Rebased-From: fa8ced32a60dea37ac169241cf9a1f708ef46c4b --- doc/reduce-traffic.md | 13 +++++++++++++ src/init.cpp | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/reduce-traffic.md b/doc/reduce-traffic.md index dd1469f5638..5a71f62e0f8 100644 --- a/doc/reduce-traffic.md +++ b/doc/reduce-traffic.md @@ -35,3 +35,16 @@ blocks and transactions to fewer nodes. Reducing the maximum connected nodes to a minimum could be desirable if traffic limits are tiny. Keep in mind that bitcoin's trustless model works best if you are connected to a handful of nodes. + +## 4. Turn off transaction relay (`-blocksonly`) + +Forwarding transactions to peers increases the P2P traffic. To only sync blocks +with other peers, you can disable transaction relay. + +Be reminded of the effects of this setting. + +- Fee estimation will no longer work. +- Not relaying other's transactions could hurt your privacy if used while a + wallet is loaded or if you use the node to broadcast transactions. +- It makes block propagation slower because compact block relay can only be + used when transaction relay is enabled. diff --git a/src/init.cpp b/src/init.cpp index 52f1f0d84f6..7cb70922544 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -376,7 +376,7 @@ void SetupServerArgs() gArgs.AddArg("-blocksdir=", "Specify blocks directory (default: /blocks)", false, OptionsCategory::OPTIONS); gArgs.AddArg("-blocknotify=", "Execute command when the best block changes (%s in cmd is replaced by block hash)", false, OptionsCategory::OPTIONS); gArgs.AddArg("-blockreconstructionextratxn=", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-blocksonly", strprintf("Whether to operate in a blocks only mode (default: %u)", DEFAULT_BLOCKSONLY), true, OptionsCategory::OPTIONS); + gArgs.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Transactions from the wallet or RPC are not affected. (default: %u)", DEFAULT_BLOCKSONLY), false, OptionsCategory::OPTIONS); gArgs.AddArg("-conf=", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), false, OptionsCategory::OPTIONS); gArgs.AddArg("-datadir=", "Specify data directory", false, OptionsCategory::OPTIONS); gArgs.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), true, OptionsCategory::OPTIONS); From eb85ee62b35c160be10487faa566ca36b5d1a19c Mon Sep 17 00:00:00 2001 From: "David A. Harding" Date: Thu, 25 Apr 2019 08:53:08 -0400 Subject: [PATCH 06/36] Doc: remove text about txes always relayed from -whitelist Updates text since -whitelistforcerelay was set to false by default in PR #15193. Github-Pull: #15890 Rebased-From: e0bb2799992afe88e6f4efc6d90ed82ddf1ec5ec --- src/init.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 7cb70922544..1ed44821e6b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -446,7 +446,7 @@ void SetupServerArgs() #endif gArgs.AddArg("-whitebind=", "Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6", false, OptionsCategory::CONNECTION); gArgs.AddArg("-whitelist=", "Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) or CIDR notated network (e.g. 1.2.3.0/24). Can be specified multiple times." - " Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway", false, OptionsCategory::CONNECTION); + " Whitelisted peers cannot be DoS banned", false, OptionsCategory::CONNECTION); g_wallet_init_interface.AddWalletOptions(); @@ -518,7 +518,7 @@ void SetupServerArgs() gArgs.AddArg("-mempoolreplacement", strprintf("Enable transaction replacement in the memory pool (default: %u)", DEFAULT_ENABLE_REPLACEMENT), false, OptionsCategory::NODE_RELAY); gArgs.AddArg("-minrelaytxfee=", 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)), false, OptionsCategory::NODE_RELAY); - gArgs.AddArg("-whitelistforcerelay", strprintf("Force relay of transactions from whitelisted peers even if they violate local relay policy (default: %d)", DEFAULT_WHITELISTFORCERELAY), false, OptionsCategory::NODE_RELAY); + gArgs.AddArg("-whitelistforcerelay", strprintf("Force relay of transactions from whitelisted peers even if the transactions were already in the mempool or violate local relay policy (default: %d)", DEFAULT_WHITELISTFORCERELAY), false, OptionsCategory::NODE_RELAY); gArgs.AddArg("-whitelistrelay", strprintf("Accept relayed transactions received from whitelisted peers even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), false, OptionsCategory::NODE_RELAY); From a635377b625f6e29c4d66939641a7c1fc27e5eb9 Mon Sep 17 00:00:00 2001 From: Daniel Kraft Date: Fri, 3 May 2019 15:10:39 +0200 Subject: [PATCH 07/36] Install bitcoin-wallet manpage. This change marks the already-existing bitcoin-wallet.1 manpage file for installation together with the others. Previously, only bitcoind.1, bitcoin-cli.1, bitcoin-tx.1 and bitcoin-qt.1 would be installed. Github-Pull: #15947 Rebased-From: 00d110463aed12ecdc6e9c2bf47d9ef61d19fa9d --- doc/man/Makefile.am | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index 9b36319e649..edbc0911a16 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -15,3 +15,9 @@ endif if BUILD_BITCOIN_TX dist_man1_MANS+=bitcoin-tx.1 endif + +if ENABLE_WALLET +if BUILD_BITCOIN_WALLET + dist_man1_MANS+=bitcoin-wallet.1 +endif +endif From 3dbc7def0f12257918b87e6abd6432d40d3e59f9 Mon Sep 17 00:00:00 2001 From: MeshCollider Date: Mon, 6 May 2019 19:49:13 +1200 Subject: [PATCH 08/36] Show loaded wallets as disabled in open menu instead of nothing Github-Pull: #15957 Rebased-From: c3ef63a52f304a600fff1f9c7caa5cb804d41d43 --- src/qt/bitcoingui.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index abf9136eee5..ff66df376b1 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -370,9 +370,18 @@ void BitcoinGUI::createActions() connect(openAction, &QAction::triggered, this, &BitcoinGUI::openClicked); connect(m_open_wallet_action->menu(), &QMenu::aboutToShow, [this] { m_open_wallet_action->menu()->clear(); - for (std::string path : m_wallet_controller->getWalletsAvailableToOpen()) { + std::vector available_wallets = m_wallet_controller->getWalletsAvailableToOpen(); + std::vector wallets = m_node.listWalletDir(); + for (const auto& path : wallets) { QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); QAction* action = m_open_wallet_action->menu()->addAction(name); + + if (std::find(available_wallets.begin(), available_wallets.end(), path) == available_wallets.end()) { + // This wallet is already loaded + action->setEnabled(false); + continue; + } + connect(action, &QAction::triggered, [this, name, path] { OpenWalletActivity* activity = m_wallet_controller->openWallet(path); @@ -400,6 +409,10 @@ void BitcoinGUI::createActions() assert(invoked); }); } + if (wallets.empty()) { + QAction* action = m_open_wallet_action->menu()->addAction(tr("No wallets available")); + action->setEnabled(false); + } }); connect(m_close_wallet_action, &QAction::triggered, [this] { m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this); From 206f5ee87576d619ea9d09380cc4d205989c7885 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 23 Aug 2018 17:01:14 -0700 Subject: [PATCH 09/36] Disallow extended encoding for non-witness transactions Github-Pull: #14039 Rebased-From: bb530efa1872ec963417f61da9a95185c7a7a7d6 --- src/primitives/transaction.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index f6f8e313632..aad991e2f15 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -222,6 +222,10 @@ inline void UnserializeTransaction(TxType& tx, Stream& s) { for (size_t i = 0; i < tx.vin.size(); i++) { s >> tx.vin[i].scriptWitness.stack; } + if (!tx.HasWitness()) { + /* It's illegal to encode witnesses when all witness stacks are empty. */ + throw std::ios_base::failure("Superfluous witness record"); + } } if (flags) { /* Unknown flag in the serialization */ From 5a58ddb6d558514c2f48543be0760bbf22698189 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Thu, 25 Apr 2019 17:01:01 -0400 Subject: [PATCH 10/36] Fix missing input template by making minimal tx Github-Pull: #15893 Rebased-From: 25b078658139c1aea58393a32ac5a79144d8d140 --- test/functional/data/invalid_txs.py | 6 +++++- test/functional/feature_block.py | 14 -------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index 02deae92f33..d262dae5aa4 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -71,9 +71,13 @@ class InputMissing(BadTxTemplate): reject_reason = "bad-txns-vin-empty" expect_disconnect = False + # We use a blank transaction here to make sure + # it is interpreted as a non-witness transaction. + # Otherwise the transaction will fail the + # "surpufluous witness" check during deserialization + # rather than the input count check. def get_tx(self): tx = CTransaction() - tx.vout.append(CTxOut(0, sc.CScript([sc.OP_TRUE] * 100))) tx.calc_sha256() return tx diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 5253ff7aaa7..c62fbb8e2bd 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -146,20 +146,6 @@ class FullBlockTest(BitcoinTestFramework): badtx = template.get_tx() if TxTemplate != invalid_txs.InputMissing: self.sign_tx(badtx, attempt_spend_tx) - else: - # Segwit is active in regtest at this point, so to deserialize a - # transaction without any inputs correctly, we set the outputs - # to an empty list. This is a hack, as the serialization of an - # empty list of outputs is deserialized as flags==0 and thus - # deserialization of the outputs is skipped. - # A policy check requires "loose" txs to be of a minimum size, - # so vtx is not set to be empty in the TxTemplate class and we - # only apply the workaround where txs are not "loose", i.e. in - # blocks. - # - # The workaround has the purpose that both sides calculate - # the same tx hash in the merkle tree - badtx.vout = [] badtx.rehash() badblock = self.update_block(blockname, [badtx]) self.sync_blocks( From 86031083c71e257778e95e21e81532b310472358 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Thu, 21 Mar 2019 13:47:47 -0400 Subject: [PATCH 11/36] Add test for superfluous witness record in deserialization Github-Pull: #15893 Rebased-From: cc556e4a30b4a32eab6722f590489d89b2875de3 --- test/functional/p2p_segwit.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 8f8e89cf15c..ff7f1dd0c9e 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -37,6 +37,7 @@ from test_framework.messages import ( ser_vector, sha256, uint256_from_str, + FromHex, ) from test_framework.mininode import ( P2PInterface, @@ -81,6 +82,7 @@ from test_framework.util import ( hex_str_to_bytes, sync_blocks, sync_mempools, + assert_raises_rpc_error, ) # The versionbit bit used to signal activation of SegWit @@ -273,6 +275,7 @@ class SegWitTest(BitcoinTestFramework): self.test_non_standard_witness() self.test_upgrade_after_activation() self.test_witness_sigops() + self.test_superfluous_witness() # Individual tests @@ -2039,5 +2042,31 @@ class SegWitTest(BitcoinTestFramework): # TODO: test p2sh sigop counting + def test_superfluous_witness(self): + # Serialization of tx that puts witness flag to 1 always + def serialize_with_bogus_witness(tx): + flags = 1 + r = b"" + r += struct.pack(" Date: Mon, 13 May 2019 15:38:10 -0400 Subject: [PATCH 12/36] Disallow extended encoding for non-witness transactions (take 3) Github-Pull: #16021 Rebased-From: fa2b52af32f6a4b9c22c270f36e92960c29ef364 --- src/net_processing.cpp | 21 ++++++++++----------- test/functional/p2p_segwit.py | 26 +++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 550be5f7ca7..ab30104c472 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -3096,23 +3096,22 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic& inter if (m_enable_bip61) { connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_MALFORMED, std::string("error parsing message"))); } - if (strstr(e.what(), "end of data")) - { + if (strstr(e.what(), "end of data")) { // Allow exceptions from under-length message on vRecv LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' caught, normally caused by a message being shorter than its stated length\n", __func__, SanitizeString(strCommand), nMessageSize, e.what()); - } - else if (strstr(e.what(), "size too large")) - { + } else if (strstr(e.what(), "size too large")) { // Allow exceptions from over-long size LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' caught\n", __func__, SanitizeString(strCommand), nMessageSize, e.what()); - } - else if (strstr(e.what(), "non-canonical ReadCompactSize()")) - { + } else if (strstr(e.what(), "non-canonical ReadCompactSize()")) { // Allow exceptions from non-canonical encoding LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' caught\n", __func__, SanitizeString(strCommand), nMessageSize, e.what()); - } - else - { + } else if (strstr(e.what(), "Superfluous witness record")) { + // Allow exceptions from illegal witness encoding + LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' caught\n", __func__, SanitizeString(strCommand), nMessageSize, e.what()); + } else if (strstr(e.what(), "Unknown transaction optional data")) { + // Allow exceptions from unknown witness encoding + LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' caught\n", __func__, SanitizeString(strCommand), nMessageSize, e.what()); + } else { PrintExceptionContinue(&e, "ProcessMessages()"); } } diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index ff7f1dd0c9e..22d3cb10147 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -2043,9 +2043,9 @@ class SegWitTest(BitcoinTestFramework): # TODO: test p2sh sigop counting def test_superfluous_witness(self): - # Serialization of tx that puts witness flag to 1 always + # Serialization of tx that puts witness flag to 3 always def serialize_with_bogus_witness(tx): - flags = 1 + flags = 3 r = b"" r += struct.pack(" Date: Sun, 19 May 2019 16:43:22 +0800 Subject: [PATCH 13/36] qt: fix opening bitcoin.conf via Preferences on macOS; see #15409 Github-Pull: #16044 Rebased-From: 6e6494b3fb345848025494cb7a79c5bf8f35e417 --- src/qt/guiutil.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index ba0a5abdf31..f6dba1006b9 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -60,6 +60,7 @@ #include #include +#include #endif namespace GUIUtil { @@ -392,7 +393,15 @@ bool openBitcoinConf() configFile.close(); /* Open bitcoin.conf with the associated application */ - return QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathConfig))); + bool res = QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathConfig))); +#ifdef Q_OS_MAC + // Workaround for macOS-specific behavior; see #15409. + if (!res) { + res = QProcess::startDetached("/usr/bin/open", QStringList{"-t", boostPathToQString(pathConfig)}); + } +#endif + + return res; } ToolTipToRichTextFilter::ToolTipToRichTextFilter(int _size_threshold, QObject *parent) : From 7ed1a601934625a8073324da6e3ec881cec01e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Wed, 29 May 2019 22:53:25 +0100 Subject: [PATCH 14/36] gui: Enable console line edit on setClientModel Github-Pull: #16122 Rebased-From: 2d8ad2f99710a8981e33fe2d6ce834b0076c4e80 --- src/qt/forms/debugwindow.ui | 3 +++ src/qt/rpcconsole.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index f0b976001e0..70ad32752a1 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -636,6 +636,9 @@ + + false + diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index fc1e14b031c..786e36e617b 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -680,6 +680,9 @@ void RPCConsole::setClientModel(ClientModel *model) wordList.sort(); autoCompleter = new QCompleter(wordList, this); autoCompleter->setModelSorting(QCompleter::CaseSensitivelySortedModel); + // ui->lineEdit is initially disabled because running commands is only + // possible from now on. + ui->lineEdit->setEnabled(true); ui->lineEdit->setCompleter(autoCompleter); autoCompleter->popup()->installEventFilter(this); // Start thread to execute RPC commands. From d80c558e026a4e500d5894d0c386378ac7a4d20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Sun, 2 Jun 2019 17:04:35 +0100 Subject: [PATCH 15/36] gui: Set progressDialog to nullptr Github-Pull: #16135 Rebased-From: d2ae6be80f6a0156021bf8c9b9d17cd4966ddffc --- src/qt/bitcoingui.cpp | 1 + src/qt/walletview.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index ff66df376b1..b5c92e10a22 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1341,6 +1341,7 @@ void BitcoinGUI::showProgress(const QString &title, int nProgress) if (progressDialog) { progressDialog->close(); progressDialog->deleteLater(); + progressDialog = nullptr; } } else if (progressDialog) { progressDialog->setValue(nProgress); diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 5f6f93d9481..be47f67f950 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -316,6 +316,7 @@ void WalletView::showProgress(const QString &title, int nProgress) if (progressDialog) { progressDialog->close(); progressDialog->deleteLater(); + progressDialog = nullptr; } } else if (progressDialog) { if (progressDialog->wasCanceled()) { From d1f261150b49a957138ef4920203f91f5a8c01b6 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 8 May 2019 13:29:44 -0700 Subject: [PATCH 16/36] Add test for GCC bug 90348 Github-Pull: #15985 Rebased-From: 58e291cfad12fa85af87d093acfa7b44702e3521 --- src/Makefile.test.include | 1 + src/test/compilerbug_tests.cpp | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/test/compilerbug_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 84bc326cfe5..0408686c176 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -78,6 +78,7 @@ BITCOIN_TESTS =\ test/bswap_tests.cpp \ test/checkqueue_tests.cpp \ test/coins_tests.cpp \ + test/compilerbug_tests.cpp \ test/compress_tests.cpp \ test/crypto_tests.cpp \ test/cuckoocache_tests.cpp \ diff --git a/src/test/compilerbug_tests.cpp b/src/test/compilerbug_tests.cpp new file mode 100644 index 00000000000..701671314f2 --- /dev/null +++ b/src/test/compilerbug_tests.cpp @@ -0,0 +1,43 @@ +// Copyright (c) 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. + +#include +#include + +BOOST_FIXTURE_TEST_SUITE(compilerbug_tests, BasicTestingSetup) + +#if defined(__GNUC__) +// This block will also be built under clang, which is fine (as it supports noinline) +void __attribute__ ((noinline)) set_one(unsigned char* ptr) +{ + *ptr = 1; +} + +int __attribute__ ((noinline)) check_zero(unsigned char const* in, unsigned int len) +{ + for (unsigned int i = 0; i < len; ++i) { + if (in[i] != 0) return 0; + } + return 1; +} + +void set_one_on_stack() { + unsigned char buf[1]; + set_one(buf); +} + +BOOST_AUTO_TEST_CASE(gccbug_90348) { + // Test for GCC bug 90348. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90348 + for (int i = 0; i <= 4; ++i) { + unsigned char in[4]; + for (int j = 0; j < i; ++j) { + in[j] = 0; + set_one_on_stack(); // Apparently modifies in[0] + } + BOOST_CHECK(check_zero(in, i)); + } +} +#endif + +BOOST_AUTO_TEST_SUITE_END() From b2398240ff901715dab39a98b04d4eb9e0a462ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Wed, 29 May 2019 14:20:59 +0100 Subject: [PATCH 17/36] gui: Enable open wallet menu on setWalletController Github-Pull: #16118 Rebased-From: 75485ef0962a53946f17b761c4445627b07e6eff --- src/qt/bitcoingui.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index b5c92e10a22..1444dddeb19 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -335,7 +335,7 @@ void BitcoinGUI::createActions() openAction->setStatusTip(tr("Open a bitcoin: URI or payment request")); m_open_wallet_action = new QAction(tr("Open Wallet"), this); - m_open_wallet_action->setMenu(new QMenu(this)); + m_open_wallet_action->setEnabled(false); m_open_wallet_action->setStatusTip(tr("Open a wallet")); m_close_wallet_action = new QAction(tr("Close Wallet..."), this); @@ -633,6 +633,9 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller) m_wallet_controller = wallet_controller; + m_open_wallet_action->setEnabled(true); + m_open_wallet_action->setMenu(new QMenu(this)); + connect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet); connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet); From c80a498ae532ad4a8e8027cd8f1057785608b3fc Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Thu, 9 May 2019 13:56:01 +0200 Subject: [PATCH 18/36] Fix RPC/pruneblockchain returned prune height Github-Pull: #15991 Rebased-From: 97f517dd851450b1ede1eb6b20f77691883a7737 --- src/rpc/blockchain.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 6fa472d4426..bd351630748 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1030,7 +1030,12 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) } PruneBlockFilesManual(height); - return uint64_t(height); + const CBlockIndex* block = ::chainActive.Tip(); + assert(block); + while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { + block = block->pprev; + } + return uint64_t(block->nHeight); } static UniValue gettxoutsetinfo(const JSONRPCRequest& request) From 592016ba18f2fbcb382ac0306ae89aba1b4be843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Sat, 25 May 2019 10:59:52 +0100 Subject: [PATCH 19/36] fixup: Fix prunning test Github-Pull: #15991 Rebased-From: f402012ccfc596d7d94851dabbf386c278ff5335 --- test/functional/feature_pruning.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 3e1ba88f0ad..b3dd699fda9 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -10,12 +10,10 @@ This test takes 30 mins or more (up to 2 hours) """ from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_greater_than, assert_raises_rpc_error, connect_nodes, mine_large_block, sync_blocks, wait_until +from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes, mine_large_block, sync_blocks, wait_until import os -MIN_BLOCKS_TO_KEEP = 288 - # Rescans start at the earliest block up to 2 hours before a key timestamp, so # the manual prune RPC avoids pruning blocks in the same window to be # compatible with pruning based on key creation time. @@ -250,20 +248,9 @@ class PruneTest(BitcoinTestFramework): else: return index - def prune(index, expected_ret=None): + def prune(index): ret = node.pruneblockchain(height=height(index)) - # Check the return value. When use_timestamp is True, just check - # that the return value is less than or equal to the expected - # value, because when more than one block is generated per second, - # a timestamp will not be granular enough to uniquely identify an - # individual block. - if expected_ret is None: - expected_ret = index - if use_timestamp: - assert_greater_than(ret, 0) - assert_greater_than(expected_ret + 1, ret) - else: - assert_equal(ret, expected_ret) + assert_equal(ret, node.getblockchaininfo()['pruneheight']) def has_block(index): return os.path.isfile(os.path.join(self.nodes[node_number].datadir, "regtest", "blocks", "blk{:05}.dat".format(index))) @@ -308,7 +295,7 @@ class PruneTest(BitcoinTestFramework): raise AssertionError("blk00001.dat is still there, should be pruned by now") # height=1000 should not prune anything more, because tip-288 is in blk00002.dat. - prune(1000, 1001 - MIN_BLOCKS_TO_KEEP) + prune(1000) if not has_block(2): raise AssertionError("blk00002.dat is still there, should be pruned by now") From d24d0ec056b05a0791e6befc8b02e680187b0ce1 Mon Sep 17 00:00:00 2001 From: Chris Moore Date: Thu, 13 Jun 2019 19:33:28 -0700 Subject: [PATCH 20/36] Add example 2nd arg to signrawtransactionwithkey The RPC examples for signrawtransactionwithkey are missing the 2nd parameter. Github-Pull: #16210 Rebased-From: 71fd628adafdeb2a4b343e0d51d7168cdb186312 --- src/rpc/rawtransaction.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 71acdbfb991..19ec6f1abe6 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -996,8 +996,8 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) "}\n" }, RPCExamples{ - HelpExampleCli("signrawtransactionwithkey", "\"myhex\"") - + HelpExampleRpc("signrawtransactionwithkey", "\"myhex\"") + HelpExampleCli("signrawtransactionwithkey", "\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") + + HelpExampleRpc("signrawtransactionwithkey", "\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"") }, }.ToString()); From bb36ac82efb576d12b7ddac2c3f9277be9a74c2e Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Fri, 26 Apr 2019 09:09:50 -0400 Subject: [PATCH 21/36] rpc: Switch touched RPCs to IsValidNumArgs Github-Pull: #15899 Rebased-From: fa5c5cd141f0265a5693234690ac757b811157d8 --- src/rpc/rawtransaction.cpp | 20 ++++++++++++-------- src/wallet/rpcwallet.cpp | 10 ++++++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 19ec6f1abe6..fdd4accacfb 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -535,9 +535,7 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) static UniValue decoderawtransaction(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw std::runtime_error( - RPCHelpMan{"decoderawtransaction", + const RPCHelpMan help{"decoderawtransaction", "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"}, @@ -589,7 +587,11 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request) HelpExampleCli("decoderawtransaction", "\"hexstring\"") + HelpExampleRpc("decoderawtransaction", "\"hexstring\"") }, - }.ToString()); + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); @@ -1643,9 +1645,7 @@ UniValue createpsbt(const JSONRPCRequest& request) UniValue converttopsbt(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( - RPCHelpMan{"converttopsbt", + const RPCHelpMan help{"converttopsbt", "\nConverts a network serialized transaction to a PSBT. This should be used only with createrawtransaction and fundrawtransaction\n" "createpsbt and walletcreatefundedpsbt should be used for new applications.\n", { @@ -1666,7 +1666,11 @@ UniValue converttopsbt(const JSONRPCRequest& request) "\nConvert the transaction to a PSBT\n" + HelpExampleCli("converttopsbt", "\"rawtransaction\"") }, - }.ToString()); + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 9393d86577c..14c06624afe 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3040,9 +3040,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( - RPCHelpMan{"fundrawtransaction", + const RPCHelpMan help{"fundrawtransaction", "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" "This will not modify existing inputs, and will add at most one change output to the outputs.\n" "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" @@ -3101,7 +3099,11 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) "\nSend the transaction\n" + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") }, - }.ToString()); + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); From 966d8d08425cce597c8429754a668d2f556870d2 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 27 Nov 2018 17:43:48 +0000 Subject: [PATCH 22/36] Bugfix: test/functional/rpc_psbt: Remove check for specific error message that depends on uncertain assumptions When converttopsbt is called with a signed transaction, it either fails with "TX decode failed" if one or more inputs were segwit, or "Inputs must not have scriptSigs and scriptWitnesses" otherwise. Since no effort is made by the test to ensure the inputs are segwit or not, avoid checking the exact message used. The error code is still checked to ensure it is of the correct kind of failure. Github-Pull: #14818 Rebased-From: 097c4aa379f255639ce0084702693fa72a595d6b --- test/functional/rpc_psbt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 53f606f2ec0..4b0480bf12a 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -145,9 +145,10 @@ class PSBTTest(BitcoinTestFramework): self.nodes[0].decodepsbt(new_psbt) # Make sure that a psbt with signatures cannot be converted + # Error could be either "TX decode failed" (segwit inputs causes parsing to fail) or "Inputs must not have scriptSigs and scriptWitnesses" signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) - assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex']) - assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'], False) + assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex']) + assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'], False) # Unless we allow it to convert and strip signatures self.nodes[0].converttopsbt(signedtx['hex'], True) From 832eb4ff54aebf86e5e0a4794ee46cf3f8b470ae Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 27 Nov 2018 17:46:20 +0000 Subject: [PATCH 23/36] Bugfix: test/functional/rpc_psbt: Correct test description comment Github-Pull: #14818 Rebased-From: c87fc71f7e9316bcc0653cd86c50177424b5b1f9 --- test/functional/rpc_psbt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 4b0480bf12a..84acd9c8250 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -144,7 +144,7 @@ class PSBTTest(BitcoinTestFramework): new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) self.nodes[0].decodepsbt(new_psbt) - # Make sure that a psbt with signatures cannot be converted + # Make sure that a non-psbt with signatures cannot be converted # Error could be either "TX decode failed" (segwit inputs causes parsing to fail) or "Inputs must not have scriptSigs and scriptWitnesses" signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex']) From 0023c978905b462e614872a7674d4225e1cf7007 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Fri, 26 Apr 2019 09:04:08 -0400 Subject: [PATCH 24/36] rpc: bugfix: Properly use iswitness in converttopsbt Also explain the param in all RPCs Github-Pull: #15899 Rebased-From: fa499b5f027f77c0bf13699852c8c06f78e27bef --- src/rpc/rawtransaction.cpp | 25 ++++++++++++++++--------- src/wallet/rpcwallet.cpp | 9 +++++++-- test/functional/rpc_psbt.py | 5 +++-- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index fdd4accacfb..ae4fed285a9 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -539,8 +539,13 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request) "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"}, - {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction\n" - " If iswitness is not present, heuristic tests will be used in decoding"}, + {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n" + "If iswitness is not present, heuristic tests will be used in decoding.\n" + "If true, only witness deserialization will be tried.\n" + "If false, only non-witness deserialization will be tried.\n" + "This boolean should reflect whether the transaction has inputs\n" + "(e.g. fully valid, or on-chain transactions), if known by the caller." + }, }, RPCResult{ "{\n" @@ -1650,12 +1655,15 @@ UniValue converttopsbt(const JSONRPCRequest& request) "createpsbt and walletcreatefundedpsbt should be used for new applications.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of a raw transaction"}, - {"permitsigdata", RPCArg::Type::BOOL, /* default */ "false", "If true, any signatures in the input will be discarded and conversion.\n" + {"permitsigdata", RPCArg::Type::BOOL, /* default */ "false", "If true, any signatures in the input will be discarded and conversion\n" " will continue. If false, RPC will fail if any signatures are present."}, {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n" - " If iswitness is not present, heuristic tests will be used in decoding. If true, only witness deserializaion\n" - " will be tried. If false, only non-witness deserialization will be tried. Only has an effect if\n" - " permitsigdata is true."}, + "If iswitness is not present, heuristic tests will be used in decoding.\n" + "If true, only witness deserialization will be tried.\n" + "If false, only non-witness deserialization will be tried.\n" + "This boolean should reflect whether the transaction has inputs\n" + "(e.g. fully valid, or on-chain transactions), if known by the caller." + }, }, RPCResult{ " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" @@ -1672,7 +1680,6 @@ UniValue converttopsbt(const JSONRPCRequest& request) throw std::runtime_error(help.ToString()); } - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); // parse hex string from parameter @@ -1680,8 +1687,8 @@ UniValue converttopsbt(const JSONRPCRequest& request) bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool(); bool witness_specified = !request.params[2].isNull(); bool iswitness = witness_specified ? request.params[2].get_bool() : false; - bool try_witness = permitsigdata ? (witness_specified ? iswitness : true) : false; - bool try_no_witness = permitsigdata ? (witness_specified ? !iswitness : true) : true; + const bool try_witness = witness_specified ? iswitness : true; + const bool try_no_witness = witness_specified ? !iswitness : true; if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 14c06624afe..f535e2b36fd 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3079,8 +3079,13 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) " \"CONSERVATIVE\""}, }, "options"}, - {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction \n" - " If iswitness is not present, heuristic tests will be used in decoding"}, + {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n" + "If iswitness is not present, heuristic tests will be used in decoding.\n" + "If true, only witness deserialization will be tried.\n" + "If false, only non-witness deserialization will be tried.\n" + "This boolean should reflect whether the transaction has inputs\n" + "(e.g. fully valid, or on-chain transactions), if known by the caller." + }, }, RPCResult{ "{\n" diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 84acd9c8250..6c0aec92288 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -146,9 +146,10 @@ class PSBTTest(BitcoinTestFramework): # Make sure that a non-psbt with signatures cannot be converted # Error could be either "TX decode failed" (segwit inputs causes parsing to fail) or "Inputs must not have scriptSigs and scriptWitnesses" + # We must set iswitness=True because the serialized transaction has inputs and is therefore a witness transaction signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) - assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex']) - assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'], False) + assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, hexstring=signedtx['hex'], iswitness=True) + assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, hexstring=signedtx['hex'], permitsigdata=False, iswitness=True) # Unless we allow it to convert and strip signatures self.nodes[0].converttopsbt(signedtx['hex'], True) From f88959ba7c30d82d7fd70a399326a4347cc36911 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Thu, 13 Jun 2019 09:04:43 -0400 Subject: [PATCH 25/36] tinyformat: Add doc to Bitcoin Core specific strprintf Github-Pull: #16205 Rebased-From: fa72a64b90dc07a80b1ca6127eb50d8244dedc3b --- src/tinyformat.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tinyformat.h b/src/tinyformat.h index 14b7cd30262..182f518a0b5 100644 --- a/src/tinyformat.h +++ b/src/tinyformat.h @@ -1063,6 +1063,7 @@ std::string format(const std::string &fmt, const Args&... args) } // namespace tinyformat +/** Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for details) */ #define strprintf tfm::format #endif // TINYFORMAT_H_INCLUDED From e29aa6e72ecf9e0530712bd6c5790b7252d9ae84 Mon Sep 17 00:00:00 2001 From: Kristaps Kaupe Date: Sun, 26 May 2019 18:35:13 +0300 Subject: [PATCH 26/36] Exceptions should be caught by reference, not by value. Github-Pull: #16095 Rebased-From: ae7faf20d5fb3e2415ccadc37100dfc44aa0cd94 --- src/wallet/wallettool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index 797f051189b..b47195cfb65 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -62,7 +62,7 @@ static std::shared_ptr LoadWallet(const std::string& name, const fs::pa try { bool first_run; load_wallet_ret = wallet_instance->LoadWallet(first_run); - } catch (const std::runtime_error) { + } catch (const std::runtime_error&) { fprintf(stderr, "Error loading %s. Is wallet being used by another process?\n", name.c_str()); return nullptr; } From beb09f09b3749563126f09dba2ac9d884c587678 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Thu, 13 Jun 2019 09:16:10 -0400 Subject: [PATCH 27/36] scripted-diff: Replace fprintf with tfm::format -BEGIN VERIFY SCRIPT- sed -i --regexp-extended -e 's/fprintf\(std(err|out), /tfm::format(std::c\1, /g' $(git grep -l 'fprintf(' -- ':(exclude)src/crypto' ':(exclude)src/leveldb' ':(exclude)src/univalue' ':(exclude)src/secp256k1') -END VERIFY SCRIPT- fixup! scripted-diff: Replace fprintf with tfm::format Github-Pull: #16205 Rebased-From: fac03ec43a15ad547161e37e53ea82482cc508f9 --- src/bench/bench_bitcoin.cpp | 4 ++-- src/bitcoin-cli.cpp | 14 +++++++------- src/bitcoin-tx.cpp | 14 +++++++------- src/bitcoin-wallet.cpp | 12 ++++++------ src/bitcoind.cpp | 18 +++++++++--------- src/noui.cpp | 2 +- src/qt/utilitydialog.cpp | 2 +- src/sync.cpp | 6 +++--- src/util/system.cpp | 4 ++-- src/wallet/wallettool.cpp | 38 ++++++++++++++++++------------------- 10 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index d67b2c5bc78..f46c5070453 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -49,7 +49,7 @@ int main(int argc, char** argv) SetupBenchArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error.c_str()); return EXIT_FAILURE; } @@ -73,7 +73,7 @@ int main(int argc, char** argv) double scaling_factor; if (!ParseDouble(scaling_str, &scaling_factor)) { - fprintf(stderr, "Error parsing scaling factor as double: %s\n", scaling_str.c_str()); + tfm::format(std::cerr, "Error parsing scaling factor as double: %s\n", scaling_str.c_str()); return EXIT_FAILURE; } diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index b0e1f67d939..774a255b682 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -101,7 +101,7 @@ static int AppInitRPC(int argc, char* argv[]) SetupCliArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error.c_str()); return EXIT_FAILURE; } if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { @@ -115,26 +115,26 @@ static int AppInitRPC(int argc, char* argv[]) strUsage += "\n" + gArgs.GetHelpMessage(); } - fprintf(stdout, "%s", strUsage.c_str()); + tfm::format(std::cout, "%s", strUsage.c_str()); if (argc < 2) { - fprintf(stderr, "Error: too few parameters\n"); + tfm::format(std::cerr, "Error: too few parameters\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } if (!fs::is_directory(GetDataDir(false))) { - fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); + tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return EXIT_FAILURE; } if (!gArgs.ReadConfigFiles(error, true)) { - fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); + tfm::format(std::cerr, "Error reading configuration file: %s\n", error.c_str()); return EXIT_FAILURE; } // Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause) try { SelectBaseParams(gArgs.GetChainName()); } catch (const std::exception& e) { - fprintf(stderr, "Error: %s\n", e.what()); + tfm::format(std::cerr, "Error: %s\n", e.what()); return EXIT_FAILURE; } return CONTINUE_EXECUTION; @@ -512,7 +512,7 @@ int main(int argc, char* argv[]) #endif SetupEnvironment(); if (!SetupNetworking()) { - fprintf(stderr, "Error: Initializing networking failed\n"); + tfm::format(std::cerr, "Error: Initializing networking failed\n"); return EXIT_FAILURE; } event_set_log_callback(&libevent_log_cb); diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 4be89aab6cf..6a7a22fb154 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -81,7 +81,7 @@ static int AppInitRawTx(int argc, char* argv[]) SetupBitcoinTxArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error.c_str()); return EXIT_FAILURE; } @@ -89,7 +89,7 @@ static int AppInitRawTx(int argc, char* argv[]) try { SelectParams(gArgs.GetChainName()); } catch (const std::exception& e) { - fprintf(stderr, "Error: %s\n", e.what()); + tfm::format(std::cerr, "Error: %s\n", e.what()); return EXIT_FAILURE; } @@ -103,10 +103,10 @@ static int AppInitRawTx(int argc, char* argv[]) "\n"; strUsage += gArgs.GetHelpMessage(); - fprintf(stdout, "%s", strUsage.c_str()); + tfm::format(std::cout, "%s", strUsage.c_str()); if (argc < 2) { - fprintf(stderr, "Error: too few parameters\n"); + tfm::format(std::cerr, "Error: too few parameters\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; @@ -722,21 +722,21 @@ static void OutputTxJSON(const CTransaction& tx) TxToUniv(tx, uint256(), entry); std::string jsonOutput = entry.write(4); - fprintf(stdout, "%s\n", jsonOutput.c_str()); + tfm::format(std::cout, "%s\n", jsonOutput.c_str()); } static void OutputTxHash(const CTransaction& tx) { std::string strHexHash = tx.GetHash().GetHex(); // the hex-encoded transaction hash (aka the transaction id) - fprintf(stdout, "%s\n", strHexHash.c_str()); + tfm::format(std::cout, "%s\n", strHexHash.c_str()); } static void OutputTxHex(const CTransaction& tx) { std::string strHex = EncodeHexTx(tx); - fprintf(stdout, "%s\n", strHex.c_str()); + tfm::format(std::cout, "%s\n", strHex.c_str()); } static void OutputTx(const CTransaction& tx) diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index 32a539aac66..ea311e7cc52 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -37,7 +37,7 @@ static bool WalletAppInit(int argc, char* argv[]) SetupWalletToolArgs(); std::string error_message; if (!gArgs.ParseParameters(argc, argv, error_message)) { - fprintf(stderr, "Error parsing command line arguments: %s\n", error_message.c_str()); + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message.c_str()); return false; } if (argc < 2 || HelpRequested(gArgs)) { @@ -49,7 +49,7 @@ static bool WalletAppInit(int argc, char* argv[]) " bitcoin-wallet [options] \n\n" + gArgs.GetHelpMessage(); - fprintf(stdout, "%s", usage.c_str()); + tfm::format(std::cout, "%s", usage.c_str()); return false; } @@ -57,7 +57,7 @@ static bool WalletAppInit(int argc, char* argv[]) LogInstance().m_print_to_console = gArgs.GetBoolArg("-printtoconsole", gArgs.GetBoolArg("-debug", false)); if (!fs::is_directory(GetDataDir(false))) { - fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); + tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return false; } // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) @@ -88,7 +88,7 @@ int main(int argc, char* argv[]) for(int i = 1; i < argc; ++i) { if (!IsSwitchChar(argv[i][0])) { if (!method.empty()) { - fprintf(stderr, "Error: two methods provided (%s and %s). Only one method should be provided.\n", method.c_str(), argv[i]); + tfm::format(std::cerr, "Error: two methods provided (%s and %s). Only one method should be provided.\n", method.c_str(), argv[i]); return EXIT_FAILURE; } method = argv[i]; @@ -96,13 +96,13 @@ int main(int argc, char* argv[]) } if (method.empty()) { - fprintf(stderr, "No method provided. Run `bitcoin-wallet -help` for valid methods.\n"); + tfm::format(std::cerr, "No method provided. Run `bitcoin-wallet -help` for valid methods.\n"); return EXIT_FAILURE; } // A name must be provided when creating a file if (method == "create" && !gArgs.IsArgSet("-wallet")) { - fprintf(stderr, "Wallet name must be provided when creating a new wallet.\n"); + tfm::format(std::cerr, "Wallet name must be provided when creating a new wallet.\n"); return EXIT_FAILURE; } diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index dde75c1b129..22120f9cf29 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -71,7 +71,7 @@ static bool AppInit(int argc, char* argv[]) SetupServerArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error.c_str()); return false; } @@ -89,7 +89,7 @@ static bool AppInit(int argc, char* argv[]) strUsage += "\n" + gArgs.GetHelpMessage(); } - fprintf(stdout, "%s", strUsage.c_str()); + tfm::format(std::cout, "%s", strUsage.c_str()); return true; } @@ -97,25 +97,25 @@ static bool AppInit(int argc, char* argv[]) { if (!fs::is_directory(GetDataDir(false))) { - fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); + tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return false; } if (!gArgs.ReadConfigFiles(error, true)) { - fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); + tfm::format(std::cerr, "Error reading configuration file: %s\n", error.c_str()); return false; } // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) try { SelectParams(gArgs.GetChainName()); } catch (const std::exception& e) { - fprintf(stderr, "Error: %s\n", e.what()); + tfm::format(std::cerr, "Error: %s\n", e.what()); return false; } // Error out when loose non-argument tokens are encountered on command line for (int i = 1; i < argc; i++) { if (!IsSwitchChar(argv[i][0])) { - fprintf(stderr, "Error: Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i]); + tfm::format(std::cerr, "Error: Command line contains unexpected token '%s', see bitcoind -h for a list of options.\n", argv[i]); return false; } } @@ -147,18 +147,18 @@ static bool AppInit(int argc, char* argv[]) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif - fprintf(stdout, "Bitcoin server starting\n"); + tfm::format(std::cout, "Bitcoin server starting\n"); // Daemonize if (daemon(1, 0)) { // don't chdir (1), do close FDs (0) - fprintf(stderr, "Error: daemon() failed: %s\n", strerror(errno)); + tfm::format(std::cerr, "Error: daemon() failed: %s\n", strerror(errno)); return false; } #if defined(MAC_OSX) #pragma GCC diagnostic pop #endif #else - fprintf(stderr, "Error: -daemon is not supported on this operating system\n"); + tfm::format(std::cerr, "Error: -daemon is not supported on this operating system\n"); return false; #endif // HAVE_DECL_DAEMON } diff --git a/src/noui.cpp b/src/noui.cpp index c7d8fee0ba7..0c18b0e231c 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -37,7 +37,7 @@ bool noui_ThreadSafeMessageBox(const std::string& message, const std::string& ca if (!fSecure) LogPrintf("%s: %s\n", strCaption, message); - fprintf(stderr, "%s: %s\n", strCaption.c_str(), message.c_str()); + tfm::format(std::cerr, "%s: %s\n", strCaption.c_str(), message.c_str()); return false; } diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index b051dd159b6..2a13c460139 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -129,7 +129,7 @@ HelpMessageDialog::~HelpMessageDialog() void HelpMessageDialog::printToConsole() { // On other operating systems, the expected action is to print the message to the console. - fprintf(stdout, "%s\n", qPrintable(text)); + tfm::format(std::cout, "%s\n", qPrintable(text)); } void HelpMessageDialog::showOrPrint() diff --git a/src/sync.cpp b/src/sync.cpp index 23ca866e530..9c42d568af0 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -105,7 +105,7 @@ static void potential_deadlock_detected(const std::pair& mismatch, LogPrintf(" %s\n", i.second.ToString()); } if (g_debug_lockorder_abort) { - fprintf(stderr, "Assertion failed: detected inconsistent lock order at %s:%i, details in debug log.\n", __FILE__, __LINE__); + tfm::format(std::cerr, "Assertion failed: detected inconsistent lock order at %s:%i, details in debug log.\n", __FILE__, __LINE__); abort(); } throw std::logic_error("potential deadlock detected"); @@ -162,7 +162,7 @@ void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, for (const std::pair& i : g_lockstack) if (i.first == cs) return; - fprintf(stderr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str()); + tfm::format(std::cerr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str()); abort(); } @@ -170,7 +170,7 @@ void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLi { for (const std::pair& i : g_lockstack) { if (i.first == cs) { - fprintf(stderr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str()); + tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str()); abort(); } } diff --git a/src/util/system.cpp b/src/util/system.cpp index 6e82de743af..7ccb9ef9e79 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -675,7 +675,7 @@ void PrintExceptionContinue(const std::exception* pex, const char* pszThread) { std::string message = FormatException(pex, pszThread); LogPrintf("\n\n************************\n%s\n", message); - fprintf(stderr, "\n\n************************\n%s\n", message.c_str()); + tfm::format(std::cerr, "\n\n************************\n%s\n", message.c_str()); } fs::path GetDefaultDataDir() @@ -935,7 +935,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) } } for (const std::string& to_include : includeconf) { - fprintf(stderr, "warning: -includeconf cannot be used from included files; ignoring -includeconf=%s\n", to_include.c_str()); + tfm::format(std::cerr, "warning: -includeconf cannot be used from included files; ignoring -includeconf=%s\n", to_include.c_str()); } } } diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index b47195cfb65..d460c514e03 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -24,7 +24,7 @@ static void WalletToolReleaseWallet(CWallet* wallet) static std::shared_ptr CreateWallet(const std::string& name, const fs::path& path) { if (fs::exists(path)) { - fprintf(stderr, "Error: File exists already\n"); + tfm::format(std::cerr, "Error: File exists already\n"); return nullptr; } // dummy chain interface @@ -33,7 +33,7 @@ static std::shared_ptr CreateWallet(const std::string& name, const fs:: bool first_run = true; DBErrors load_wallet_ret = wallet_instance->LoadWallet(first_run); if (load_wallet_ret != DBErrors::LOAD_OK) { - fprintf(stderr, "Error creating %s", name.c_str()); + tfm::format(std::cerr, "Error creating %s", name.c_str()); return nullptr; } @@ -43,7 +43,7 @@ static std::shared_ptr CreateWallet(const std::string& name, const fs:: CPubKey seed = wallet_instance->GenerateNewSeed(); wallet_instance->SetHDSeed(seed); - fprintf(stdout, "Topping up keypool...\n"); + tfm::format(std::cout, "Topping up keypool...\n"); wallet_instance->TopUpKeyPool(); return wallet_instance; } @@ -51,7 +51,7 @@ static std::shared_ptr CreateWallet(const std::string& name, const fs:: static std::shared_ptr LoadWallet(const std::string& name, const fs::path& path) { if (!fs::exists(path)) { - fprintf(stderr, "Error: Wallet files does not exist\n"); + tfm::format(std::cerr, "Error: Wallet files does not exist\n"); return nullptr; } @@ -63,28 +63,28 @@ static std::shared_ptr LoadWallet(const std::string& name, const fs::pa bool first_run; load_wallet_ret = wallet_instance->LoadWallet(first_run); } catch (const std::runtime_error&) { - fprintf(stderr, "Error loading %s. Is wallet being used by another process?\n", name.c_str()); + tfm::format(std::cerr, "Error loading %s. Is wallet being used by another process?\n", name.c_str()); return nullptr; } if (load_wallet_ret != DBErrors::LOAD_OK) { wallet_instance = nullptr; if (load_wallet_ret == DBErrors::CORRUPT) { - fprintf(stderr, "Error loading %s: Wallet corrupted", name.c_str()); + tfm::format(std::cerr, "Error loading %s: Wallet corrupted", name.c_str()); return nullptr; } else if (load_wallet_ret == DBErrors::NONCRITICAL_ERROR) { - fprintf(stderr, "Error reading %s! All keys read correctly, but transaction data" + tfm::format(std::cerr, "Error reading %s! All keys read correctly, but transaction data" " or address book entries might be missing or incorrect.", name.c_str()); } else if (load_wallet_ret == DBErrors::TOO_NEW) { - fprintf(stderr, "Error loading %s: Wallet requires newer version of %s", + tfm::format(std::cerr, "Error loading %s: Wallet requires newer version of %s", name.c_str(), PACKAGE_NAME); return nullptr; } else if (load_wallet_ret == DBErrors::NEED_REWRITE) { - fprintf(stderr, "Wallet needed to be rewritten: restart %s to complete", PACKAGE_NAME); + tfm::format(std::cerr, "Wallet needed to be rewritten: restart %s to complete", PACKAGE_NAME); return nullptr; } else { - fprintf(stderr, "Error loading %s", name.c_str()); + tfm::format(std::cerr, "Error loading %s", name.c_str()); return nullptr; } } @@ -96,12 +96,12 @@ static void WalletShowInfo(CWallet* wallet_instance) { LOCK(wallet_instance->cs_wallet); - fprintf(stdout, "Wallet info\n===========\n"); - fprintf(stdout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no"); - fprintf(stdout, "HD (hd seed available): %s\n", wallet_instance->GetHDChain().seed_id.IsNull() ? "no" : "yes"); - fprintf(stdout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize()); - fprintf(stdout, "Transactions: %zu\n", wallet_instance->mapWallet.size()); - fprintf(stdout, "Address Book: %zu\n", wallet_instance->mapAddressBook.size()); + tfm::format(std::cout, "Wallet info\n===========\n"); + tfm::format(std::cout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no"); + tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->GetHDChain().seed_id.IsNull() ? "no" : "yes"); + tfm::format(std::cout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize()); + tfm::format(std::cout, "Transactions: %zu\n", wallet_instance->mapWallet.size()); + tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->mapAddressBook.size()); } bool ExecuteWalletToolFunc(const std::string& command, const std::string& name) @@ -116,12 +116,12 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name) } } else if (command == "info") { if (!fs::exists(path)) { - fprintf(stderr, "Error: no wallet file at %s\n", name.c_str()); + tfm::format(std::cerr, "Error: no wallet file at %s\n", name.c_str()); return false; } std::string error; if (!WalletBatch::VerifyEnvironment(path, error)) { - fprintf(stderr, "Error loading %s. Is wallet being used by other process?\n", name.c_str()); + tfm::format(std::cerr, "Error loading %s. Is wallet being used by other process?\n", name.c_str()); return false; } std::shared_ptr wallet_instance = LoadWallet(name, path); @@ -129,7 +129,7 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name) WalletShowInfo(wallet_instance.get()); wallet_instance->Flush(true); } else { - fprintf(stderr, "Invalid command: %s\n", command.c_str()); + tfm::format(std::cerr, "Invalid command: %s\n", command.c_str()); return false; } From 79745d1752d0d1a95f073a04d6f4b9715745cf17 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Thu, 13 Jun 2019 09:43:24 -0400 Subject: [PATCH 28/36] Replace remaining fprintf with tfm::format manually Github-Pull: #16205 Rebased-From: fa8f195195945ce6258199af0461e3fbfbc1236d --- src/bitcoin-cli.cpp | 2 +- src/bitcoin-tx.cpp | 2 +- src/init.cpp | 7 +++---- test/lint/lint-format-strings.sh | 1 + test/lint/lint-locale-dependence.sh | 6 ++---- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 774a255b682..76cc19cc881 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -499,7 +499,7 @@ static int CommandLineRPC(int argc, char *argv[]) } if (strPrint != "") { - fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); + tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint.c_str()); } return nRet; } diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 6a7a22fb154..dcf6dddf291 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -827,7 +827,7 @@ static int CommandLineRawTx(int argc, char* argv[]) } if (strPrint != "") { - fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); + tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint.c_str()); } return nRet; } diff --git a/src/init.cpp b/src/init.cpp index 1ed44821e6b..557102efc4c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -106,14 +106,13 @@ static fs::path GetPidFile() NODISCARD static bool CreatePidFile() { - FILE* file = fsbridge::fopen(GetPidFile(), "w"); + fsbridge::ofstream file{GetPidFile()}; if (file) { #ifdef WIN32 - fprintf(file, "%d\n", GetCurrentProcessId()); + tfm::format(file, "%d\n", GetCurrentProcessId()); #else - fprintf(file, "%d\n", getpid()); + tfm::format(file, "%d\n", getpid()); #endif - fclose(file); return true; } else { return InitError(strprintf(_("Unable to create the PID file '%s': %s"), GetPidFile().string(), std::strerror(errno))); diff --git a/test/lint/lint-format-strings.sh b/test/lint/lint-format-strings.sh index c994ae3f4de..cb630c78ade 100755 --- a/test/lint/lint-format-strings.sh +++ b/test/lint/lint-format-strings.sh @@ -13,6 +13,7 @@ export LC_ALL=C FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS=( "FatalError,0" "fprintf,1" + "tfm::format,1" # Assuming tfm::::format(std::ostream&, ... "LogConnectFailure,1" "LogPrint,1" "LogPrintf,0" diff --git a/test/lint/lint-locale-dependence.sh b/test/lint/lint-locale-dependence.sh index 2b6c78c2c84..9a1aa766f79 100755 --- a/test/lint/lint-locale-dependence.sh +++ b/test/lint/lint-locale-dependence.sh @@ -8,7 +8,6 @@ KNOWN_VIOLATIONS=( "src/dbwrapper.cpp:.*vsnprintf" "src/httprpc.cpp.*trim" "src/init.cpp:.*atoi" - "src/init.cpp:.*fprintf" "src/qt/rpcconsole.cpp:.*atoi" "src/rest.cpp:.*strtol" "src/test/dbwrapper_tests.cpp:.*snprintf" @@ -85,7 +84,7 @@ LOCALE_DEPENDENT_FUNCTIONS=( mbtowc # LC_CTYPE mktime normalize # boost::locale::normalize -# printf # LC_NUMERIC + printf # LC_NUMERIC putwc putwchar scanf # LC_NUMERIC @@ -189,8 +188,7 @@ GIT_GREP_OUTPUT=$(git grep -E "[^a-zA-Z0-9_\`'\"<>](${REGEXP_LOCALE_DEPENDENT_FU EXIT_CODE=0 for LOCALE_DEPENDENT_FUNCTION in "${LOCALE_DEPENDENT_FUNCTIONS[@]}"; do MATCHES=$(grep -E "[^a-zA-Z0-9_\`'\"<>]${LOCALE_DEPENDENT_FUNCTION}(_r|_s)?[^a-zA-Z0-9_\`'\"<>]" <<< "${GIT_GREP_OUTPUT}" | \ - grep -vE "\.(c|cpp|h):\s*(//|\*|/\*|\").*${LOCALE_DEPENDENT_FUNCTION}" | \ - grep -vE 'fprintf\(.*(stdout|stderr)') + grep -vE "\.(c|cpp|h):\s*(//|\*|/\*|\").*${LOCALE_DEPENDENT_FUNCTION}") if [[ ${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES} != "" ]]; then MATCHES=$(grep -vE "${REGEXP_IGNORE_EXTERNAL_DEPENDENCIES}" <<< "${MATCHES}") fi From 13b3bb564485b239822fb8bbdab7ce528f0f0d6f Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Tue, 16 Apr 2019 13:06:37 -0400 Subject: [PATCH 29/36] test: Fixup creatmultisig documentation and whitespace Github-Pull: #15831 Rebased-From: fad81d870aa6dd25d4fab5faad4c956ba364734a --- test/functional/rpc_createmultisig.py | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 3cc35a7b9a4..553be2d2bdd 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2018 The Bitcoin Core developers +# Copyright (c) 2015-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. -"""Test transaction signing using the signrawtransaction* RPCs.""" +"""Test multisig RPCs""" from test_framework.test_framework import BitcoinTestFramework import decimal + class RpcCreateMultiSigTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -17,21 +18,21 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): def get_keys(self): node0, node1, node2 = self.nodes - self.add = [node1.getnewaddress() for _ in range(self.nkeys)] - self.pub = [node1.getaddressinfo(a)["pubkey"] for a in self.add] - self.priv = [node1.dumpprivkey(a) for a in self.add] + add = [node1.getnewaddress() for _ in range(self.nkeys)] + self.pub = [node1.getaddressinfo(a)["pubkey"] for a in add] + self.priv = [node1.dumpprivkey(a) for a in add] self.final = node2.getnewaddress() def run_test(self): - node0,node1,node2 = self.nodes + node0, node1, node2 = self.nodes # 50 BTC each, rest will be 25 BTC each node0.generate(149) self.sync_all() self.moved = 0 - for self.nkeys in [3,5]: - for self.nsigs in [2,3]: + for self.nkeys in [3, 5]: + for self.nsigs in [2, 3]: for self.output_type in ["bech32", "p2sh-segwit", "legacy"]: self.get_keys() self.do_multisig() @@ -39,7 +40,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): self.checkbalances() def checkbalances(self): - node0,node1,node2 = self.nodes + node0, node1, node2 = self.nodes node0.generate(100) self.sync_all() @@ -49,13 +50,13 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): height = node0.getblockchaininfo()["blocks"] assert 150 < height < 350 - total = 149*50 + (height-149-100)*25 + total = 149 * 50 + (height - 149 - 100) * 25 assert bal1 == 0 assert bal2 == self.moved - assert bal0+bal1+bal2 == total + assert bal0 + bal1 + bal2 == total def do_multisig(self): - node0,node1,node2 = self.nodes + node0, node1, node2 = self.nodes msig = node2.createmultisig(self.nsigs, self.pub, self.output_type) madd = msig["address"] @@ -74,7 +75,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): txid = node0.sendtoaddress(madd, 40) tx = node0.getrawtransaction(txid, True) - vout = [v["n"] for v in tx["vout"] if madd in v["scriptPubKey"].get("addresses",[])] + vout = [v["n"] for v in tx["vout"] if madd in v["scriptPubKey"].get("addresses", [])] assert len(vout) == 1 vout = vout[0] scriptPubKey = tx["vout"][vout]["scriptPubKey"]["hex"] @@ -86,7 +87,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): outval = value - decimal.Decimal("0.00001000") rawtx = node2.createrawtransaction([{"txid": txid, "vout": vout}], [{self.final: outval}]) - rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs-1], prevtxs) + rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs - 1], prevtxs) rawtx3 = node2.signrawtransactionwithkey(rawtx2["hex"], [self.priv[-1]], prevtxs) self.moved += outval @@ -97,5 +98,6 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): txinfo = node0.getrawtransaction(tx, True, blk) self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"])) + if __name__ == '__main__': RpcCreateMultiSigTest().main() From 23ba460c1abe1e089c64e87abe919e87a320b32d Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Tue, 16 Apr 2019 13:12:54 -0400 Subject: [PATCH 30/36] test: Add test that addmultisigaddress fails for watchonly addresses Github-Pull: #15831 Rebased-From: fab6a0a659bb856e4598af3e0679fc37d5239478 --- test/functional/rpc_createmultisig.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 553be2d2bdd..6411b0e2853 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -5,6 +5,9 @@ """Test multisig RPCs""" from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_raises_rpc_error, +) import decimal @@ -26,7 +29,9 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): def run_test(self): node0, node1, node2 = self.nodes - # 50 BTC each, rest will be 25 BTC each + self.check_addmultisigaddress_errors() + + self.log.info('Generating blocks ...') node0.generate(149) self.sync_all() @@ -39,6 +44,15 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): self.checkbalances() + def check_addmultisigaddress_errors(self): + self.log.info('Check that addmultisigaddress fails when the private keys are missing') + addresses = [self.nodes[1].getnewaddress(address_type='legacy') for _ in range(2)] + assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses)) + for a in addresses: + # Importing all addresses should not change the result + self.nodes[0].importaddress(a) + assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses)) + def checkbalances(self): node0, node1, node2 = self.nodes node0.generate(100) From d9fc969e71c1f0f0a2404c3bb08aad78b6ac7a39 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 15 Apr 2019 16:49:18 -0700 Subject: [PATCH 31/36] Pure python EC This removes the dependency on OpenSSL for the interaction tests, by providing a pure-Python toy implementation of secp256k1. Github-Pull: #15826 Rebased-From: 8c7b9324ca3f3ffb178bea56a96ea23f7e0383cb --- test/functional/feature_assumevalid.py | 8 +- test/functional/feature_block.py | 12 +- test/functional/p2p_segwit.py | 23 +- test/functional/test_framework/key.py | 500 +++++++++++++++---------- test/lint/lint-python-dead-code.sh | 2 +- 5 files changed, 332 insertions(+), 213 deletions(-) diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 0eb3dd440bc..7d6a56919d1 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -32,7 +32,7 @@ Start three nodes: import time from test_framework.blocktools import (create_block, create_coinbase) -from test_framework.key import CECKey +from test_framework.key import ECKey from test_framework.messages import ( CBlockHeader, COutPoint, @@ -104,9 +104,9 @@ class AssumeValidTest(BitcoinTestFramework): self.blocks = [] # Get a pubkey for the coinbase TXO - coinbase_key = CECKey() - coinbase_key.set_secretbytes(b"horsebattery") - coinbase_pubkey = coinbase_key.get_pubkey() + coinbase_key = ECKey() + coinbase_key.generate() + coinbase_pubkey = coinbase_key.get_pubkey().get_bytes() # Create the first block with a coinbase output to our key height = 1 diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index c62fbb8e2bd..1aa5ae1423e 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -14,7 +14,7 @@ from test_framework.blocktools import ( get_legacy_sigopcount_block, MAX_BLOCK_SIGOPS, ) -from test_framework.key import CECKey +from test_framework.key import ECKey from test_framework.messages import ( CBlock, COIN, @@ -86,9 +86,9 @@ class FullBlockTest(BitcoinTestFramework): self.bootstrap_p2p() # Add one p2p connection to the node self.block_heights = {} - self.coinbase_key = CECKey() - self.coinbase_key.set_secretbytes(b"horsebattery") - self.coinbase_pubkey = self.coinbase_key.get_pubkey() + self.coinbase_key = ECKey() + self.coinbase_key.generate() + self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes() self.tip = None self.blocks = {} self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) @@ -514,7 +514,7 @@ class FullBlockTest(BitcoinTestFramework): tx.vin.append(CTxIn(COutPoint(b39.vtx[i].sha256, 0), b'')) # Note: must pass the redeem_script (not p2sh_script) to the signature hash function (sighash, err) = SignatureHash(redeem_script, tx, 1, SIGHASH_ALL) - sig = self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL])) + sig = self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL])) scriptSig = CScript([sig, redeem_script]) tx.vin[1].scriptSig = scriptSig @@ -1270,7 +1270,7 @@ class FullBlockTest(BitcoinTestFramework): tx.vin[0].scriptSig = CScript() return (sighash, err) = SignatureHash(spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL) - tx.vin[0].scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) + tx.vin[0].scriptSig = CScript([self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))]) def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])): tx = self.create_tx(spend_tx, 0, value, script) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index 22d3cb10147..62b12507ed3 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -10,7 +10,7 @@ import struct import time from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment, get_witness_script, WITNESS_COMMITMENT_HEADER -from test_framework.key import CECKey, CPubKey +from test_framework.key import ECKey from test_framework.messages import ( BIP125_SEQUENCE_NUMBER, CBlock, @@ -106,7 +106,7 @@ def get_p2pkh_script(pubkeyhash): def sign_p2pk_witness_input(script, tx_to, in_idx, hashtype, value, key): """Add signature for a P2PK witness program.""" tx_hash = SegwitVersion1SignatureHash(script, tx_to, in_idx, hashtype, value) - signature = key.sign(tx_hash) + chr(hashtype).encode('latin-1') + signature = key.sign_ecdsa(tx_hash) + chr(hashtype).encode('latin-1') tx_to.wit.vtxinwit[in_idx].scriptWitness.stack = [signature, script] tx_to.rehash() @@ -1486,10 +1486,9 @@ class SegWitTest(BitcoinTestFramework): # Segwit transactions using uncompressed pubkeys are not accepted # under default policy, but should still pass consensus. - key = CECKey() - key.set_secretbytes(b"9") - key.set_compressed(False) - pubkey = CPubKey(key.get_pubkey()) + key = ECKey() + key.generate(False) + pubkey = key.get_pubkey().get_bytes() assert_equal(len(pubkey), 65) # This should be an uncompressed pubkey utxo = self.utxo.pop(0) @@ -1519,7 +1518,7 @@ class SegWitTest(BitcoinTestFramework): tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_wsh)) script = get_p2pkh_script(pubkeyhash) sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL tx2.wit.vtxinwit.append(CTxInWitness()) tx2.wit.vtxinwit[0].scriptWitness.stack = [signature, pubkey] tx2.rehash() @@ -1573,7 +1572,7 @@ class SegWitTest(BitcoinTestFramework): tx5.vin.append(CTxIn(COutPoint(tx4.sha256, 0), b"")) tx5.vout.append(CTxOut(tx4.vout[0].nValue - 1000, CScript([OP_TRUE]))) (sig_hash, err) = SignatureHash(script_pubkey, tx5, 0, SIGHASH_ALL) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL tx5.vin[0].scriptSig = CScript([signature, pubkey]) tx5.rehash() # Should pass policy and consensus. @@ -1586,9 +1585,9 @@ class SegWitTest(BitcoinTestFramework): @subtest def test_signature_version_1(self): - key = CECKey() - key.set_secretbytes(b"9") - pubkey = CPubKey(key.get_pubkey()) + key = ECKey() + key.generate() + pubkey = key.get_pubkey().get_bytes() witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)]) witness_hash = sha256(witness_program) @@ -1723,7 +1722,7 @@ class SegWitTest(BitcoinTestFramework): script = get_p2pkh_script(pubkeyhash) sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL # Check that we can't have a scriptSig tx2.vin[0].scriptSig = CScript([signature, pubkey]) diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index 1b3e510dc44..996de5f94fb 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -1,226 +1,346 @@ -# Copyright (c) 2011 Sam Rushing -"""ECC secp256k1 OpenSSL wrapper. +# Copyright (c) 2019 Pieter Wuille -WARNING: This module does not mlock() secrets; your private keys may end up on -disk in swap! Use with caution! +"""Test-only secp256k1 elliptic curve implementation -This file is modified from python-bitcoinlib. +WARNING: This code is slow, uses bad randomness, does not properly protect +keys, and is trivially vulnerable to side channel attacks. Do not use for +anything but tests. """ -import ctypes -import ctypes.util -import hashlib +import random -ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library ('ssl') or 'libeay32') +def modinv(a, n): + """Compute the modular inverse of a modulo n -ssl.BN_new.restype = ctypes.c_void_p -ssl.BN_new.argtypes = [] + See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers + """ + t1, t2 = 0, 1 + r1, r2 = n, a + while r2 != 0: + q = r1 // r2 + t1, t2 = t2, t1 - q * t2 + r1, r2 = r2, r1 - q * r2 + if r1 > 1: + return None + if t1 < 0: + t1 += n + return t1 -ssl.BN_bin2bn.restype = ctypes.c_void_p -ssl.BN_bin2bn.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p] +def jacobi_symbol(n, k): + """Compute the Jacobi symbol of n modulo k -ssl.BN_CTX_free.restype = None -ssl.BN_CTX_free.argtypes = [ctypes.c_void_p] + See http://en.wikipedia.org/wiki/Jacobi_symbol + """ + assert k > 0 and k & 1 + n %= k + t = 0 + while n != 0: + while n & 1 == 0: + n >>= 1 + r = k & 7 + t ^= (r == 3 or r == 5) + n, k = k, n + t ^= (n & k & 3 == 3) + n = n % k + if k == 1: + return -1 if t else 1 + return 0 -ssl.BN_CTX_new.restype = ctypes.c_void_p -ssl.BN_CTX_new.argtypes = [] +def modsqrt(a, p): + """Compute the square root of a modulo p -ssl.ECDH_compute_key.restype = ctypes.c_int -ssl.ECDH_compute_key.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p] + For p = 3 mod 4, if a square root exists, it is equal to a**((p+1)/4) mod p. + """ + assert(p % 4 == 3) # Only p = 3 mod 4 is implemented + sqrt = pow(a, (p + 1)//4, p) + if pow(sqrt, 2, p) == a % p: + return sqrt + return None -ssl.ECDSA_sign.restype = ctypes.c_int -ssl.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +class EllipticCurve: + def __init__(self, p, a, b): + """Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p).""" + self.p = p + self.a = a % p + self.b = b % p -ssl.ECDSA_verify.restype = ctypes.c_int -ssl.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p] + def affine(self, p1): + """Convert a Jacobian point tuple p1 to affine form, or None if at infinity.""" + x1, y1, z1 = p1 + if z1 == 0: + return None + inv = modinv(z1, self.p) + inv_2 = (inv**2) % self.p + inv_3 = (inv_2 * inv) % self.p + return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1) -ssl.EC_KEY_free.restype = None -ssl.EC_KEY_free.argtypes = [ctypes.c_void_p] + def negate(self, p1): + """Negate a Jacobian point tuple p1.""" + x1, y1, z1 = p1 + return (x1, (self.p - y1) % self.p, z1) -ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p -ssl.EC_KEY_new_by_curve_name.argtypes = [ctypes.c_int] + def on_curve(self, p1): + """Determine whether a Jacobian tuple p is on the curve (and not infinity)""" + x1, y1, z1 = p1 + z2 = pow(z1, 2, self.p) + z4 = pow(z2, 2, self.p) + return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0 -ssl.EC_KEY_get0_group.restype = ctypes.c_void_p -ssl.EC_KEY_get0_group.argtypes = [ctypes.c_void_p] + def is_x_coord(self, x): + """Test whether x is a valid X coordinate on the curve.""" + x_3 = pow(x, 3, self.p) + return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1 -ssl.EC_KEY_get0_public_key.restype = ctypes.c_void_p -ssl.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p] + def lift_x(self, x): + """Given an X coordinate on the curve, return a corresponding affine point.""" + x_3 = pow(x, 3, self.p) + v = x_3 + self.a * x + self.b + y = modsqrt(v, self.p) + if y is None: + return None + return (x, y, 1) -ssl.EC_KEY_set_private_key.restype = ctypes.c_int -ssl.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p] + def double(self, p1): + """Double a Jacobian tuple p1""" + x1, y1, z1 = p1 + if z1 == 0: + return (0, 1, 0) + y1_2 = (y1**2) % self.p + y1_4 = (y1_2**2) % self.p + x1_2 = (x1**2) % self.p + s = (4*x1*y1_2) % self.p + m = 3*x1_2 + if self.a: + m += self.a * pow(z1, 4, self.p) + m = m % self.p + x2 = (m**2 - 2*s) % self.p + y2 = (m*(s - x2) - 8*y1_4) % self.p + z2 = (2*y1*z1) % self.p + return (x2, y2, z2) -ssl.EC_KEY_set_conv_form.restype = None -ssl.EC_KEY_set_conv_form.argtypes = [ctypes.c_void_p, ctypes.c_int] + def add_mixed(self, p1, p2): + """Add a Jacobian tuple p1 and an affine tuple p2""" + x1, y1, z1 = p1 + x2, y2, z2 = p2 + assert(z2 == 1) + if z1 == 0: + return p2 + z1_2 = (z1**2) % self.p + z1_3 = (z1_2 * z1) % self.p + u2 = (x2 * z1_2) % self.p + s2 = (y2 * z1_3) % self.p + if x1 == u2: + if (y1 != s2): + return (0, 1, 0) + return self.double(p1) + h = u2 - x1 + r = s2 - y1 + h_2 = (h**2) % self.p + h_3 = (h_2 * h) % self.p + u1_h_2 = (x1 * h_2) % self.p + x3 = (r**2 - h_3 - 2*u1_h_2) % self.p + y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p + z3 = (h*z1) % self.p + return (x3, y3, z3) -ssl.EC_KEY_set_public_key.restype = ctypes.c_int -ssl.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p] + def add(self, p1, p2): + """Add two Jacobian tuples p1 and p2""" + x1, y1, z1 = p1 + x2, y2, z2 = p2 + if z1 == 0: + return p2 + if z2 == 0: + return p1 + if z1 == 1: + return self.add_mixed(p2, p1) + if z2 == 1: + return self.add_mixed(p1, p2) + z1_2 = (z1**2) % self.p + z1_3 = (z1_2 * z1) % self.p + z2_2 = (z2**2) % self.p + z2_3 = (z2_2 * z2) % self.p + u1 = (x1 * z2_2) % self.p + u2 = (x2 * z1_2) % self.p + s1 = (y1 * z2_3) % self.p + s2 = (y2 * z1_3) % self.p + if u1 == u2: + if (s1 != s2): + return (0, 1, 0) + return self.double(p1) + h = u2 - u1 + r = s2 - s1 + h_2 = (h**2) % self.p + h_3 = (h_2 * h) % self.p + u1_h_2 = (u1 * h_2) % self.p + x3 = (r**2 - h_3 - 2*u1_h_2) % self.p + y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p + z3 = (h*z1*z2) % self.p + return (x3, y3, z3) -ssl.i2o_ECPublicKey.restype = ctypes.c_void_p -ssl.i2o_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p] + def mul(self, ps): + """Compute a (multi) point multiplication -ssl.EC_POINT_new.restype = ctypes.c_void_p -ssl.EC_POINT_new.argtypes = [ctypes.c_void_p] - -ssl.EC_POINT_free.restype = None -ssl.EC_POINT_free.argtypes = [ctypes.c_void_p] - -ssl.EC_POINT_mul.restype = ctypes.c_int -ssl.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - -# this specifies the curve used with ECDSA. -NID_secp256k1 = 714 # from openssl/obj_mac.h + ps is a list of (Jacobian tuple, scalar) pairs. + """ + r = (0, 1, 0) + for i in range(255, -1, -1): + r = self.double(r) + for (p, n) in ps: + if ((n >> i) & 1): + r = self.add(r, p) + return r +SECP256K1 = EllipticCurve(2**256 - 2**32 - 977, 0, 7) +SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1) SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 -# Thx to Sam Devlin for the ctypes magic 64-bit fix. -def _check_result(val, func, args): - if val == 0: - raise ValueError - else: - return ctypes.c_void_p (val) - -ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p -ssl.EC_KEY_new_by_curve_name.errcheck = _check_result - -class CECKey(): - """Wrapper around OpenSSL's EC_KEY""" - - POINT_CONVERSION_COMPRESSED = 2 - POINT_CONVERSION_UNCOMPRESSED = 4 +class ECPubKey(): + """A secp256k1 public key""" def __init__(self): - self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1) + """Construct an uninitialized public key""" + self.valid = False - def __del__(self): - if ssl: - ssl.EC_KEY_free(self.k) - self.k = None - - def set_secretbytes(self, secret): - priv_key = ssl.BN_bin2bn(secret, 32, ssl.BN_new()) - group = ssl.EC_KEY_get0_group(self.k) - pub_key = ssl.EC_POINT_new(group) - ctx = ssl.BN_CTX_new() - if not ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx): - raise ValueError("Could not derive public key from the supplied secret.") - ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx) - ssl.EC_KEY_set_private_key(self.k, priv_key) - ssl.EC_KEY_set_public_key(self.k, pub_key) - ssl.EC_POINT_free(pub_key) - ssl.BN_CTX_free(ctx) - return self.k - - def set_privkey(self, key): - self.mb = ctypes.create_string_buffer(key) - return ssl.d2i_ECPrivateKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key)) - - def set_pubkey(self, key): - self.mb = ctypes.create_string_buffer(key) - return ssl.o2i_ECPublicKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key)) - - def get_privkey(self): - size = ssl.i2d_ECPrivateKey(self.k, 0) - mb_pri = ctypes.create_string_buffer(size) - ssl.i2d_ECPrivateKey(self.k, ctypes.byref(ctypes.pointer(mb_pri))) - return mb_pri.raw - - def get_pubkey(self): - size = ssl.i2o_ECPublicKey(self.k, 0) - mb = ctypes.create_string_buffer(size) - ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb))) - return mb.raw - - def get_raw_ecdh_key(self, other_pubkey): - ecdh_keybuffer = ctypes.create_string_buffer(32) - r = ssl.ECDH_compute_key(ctypes.pointer(ecdh_keybuffer), 32, - ssl.EC_KEY_get0_public_key(other_pubkey.k), - self.k, 0) - if r != 32: - raise Exception('CKey.get_ecdh_key(): ECDH_compute_key() failed') - return ecdh_keybuffer.raw - - def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()): - # FIXME: be warned it's not clear what the kdf should be as a default - r = self.get_raw_ecdh_key(other_pubkey) - return kdf(r) - - def sign(self, hash, low_s = True): - # FIXME: need unit tests for below cases - if not isinstance(hash, bytes): - raise TypeError('Hash must be bytes instance; got %r' % hash.__class__) - if len(hash) != 32: - raise ValueError('Hash must be exactly 32 bytes long') - - sig_size0 = ctypes.c_uint32() - sig_size0.value = ssl.ECDSA_size(self.k) - mb_sig = ctypes.create_string_buffer(sig_size0.value) - result = ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k) - assert 1 == result - assert mb_sig.raw[0] == 0x30 - assert mb_sig.raw[1] == sig_size0.value - 2 - total_size = mb_sig.raw[1] - assert mb_sig.raw[2] == 2 - r_size = mb_sig.raw[3] - assert mb_sig.raw[4 + r_size] == 2 - s_size = mb_sig.raw[5 + r_size] - s_value = int.from_bytes(mb_sig.raw[6+r_size:6+r_size+s_size], byteorder='big') - if (not low_s) or s_value <= SECP256K1_ORDER_HALF: - return mb_sig.raw[:sig_size0.value] + def set(self, data): + """Construct a public key from a serialization in compressed or uncompressed format""" + if (len(data) == 65 and data[0] == 0x04): + p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1) + self.valid = SECP256K1.on_curve(p) + if self.valid: + self.p = p + self.compressed = False + elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)): + x = int.from_bytes(data[1:33], 'big') + if SECP256K1.is_x_coord(x): + p = SECP256K1.lift_x(x) + if (p[1] & 1) != (data[0] & 1): + p = SECP256K1.negate(p) + self.p = p + self.valid = True + self.compressed = True + else: + self.valid = False else: - low_s_value = SECP256K1_ORDER - s_value - low_s_bytes = (low_s_value).to_bytes(33, byteorder='big') - while len(low_s_bytes) > 1 and low_s_bytes[0] == 0 and low_s_bytes[1] < 0x80: - low_s_bytes = low_s_bytes[1:] - new_s_size = len(low_s_bytes) - new_total_size_byte = (total_size + new_s_size - s_size).to_bytes(1,byteorder='big') - new_s_size_byte = (new_s_size).to_bytes(1,byteorder='big') - return b'\x30' + new_total_size_byte + mb_sig.raw[2:5+r_size] + new_s_size_byte + low_s_bytes - - def verify(self, hash, sig): - """Verify a DER signature""" - return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1 - - def set_compressed(self, compressed): - if compressed: - form = self.POINT_CONVERSION_COMPRESSED - else: - form = self.POINT_CONVERSION_UNCOMPRESSED - ssl.EC_KEY_set_conv_form(self.k, form) - - -class CPubKey(bytes): - """An encapsulated public key - - Attributes: - - is_valid - Corresponds to CPubKey.IsValid() - is_fullyvalid - Corresponds to CPubKey.IsFullyValid() - is_compressed - Corresponds to CPubKey.IsCompressed() - """ - - def __new__(cls, buf, _cec_key=None): - self = super(CPubKey, cls).__new__(cls, buf) - if _cec_key is None: - _cec_key = CECKey() - self._cec_key = _cec_key - self.is_fullyvalid = _cec_key.set_pubkey(self) != 0 - return self - - @property - def is_valid(self): - return len(self) > 0 + self.valid = False @property def is_compressed(self): - return len(self) == 33 + return self.compressed - def verify(self, hash, sig): - return self._cec_key.verify(hash, sig) + @property + def is_valid(self): + return self.valid - def __str__(self): - return repr(self) + def get_bytes(self): + assert(self.valid) + p = SECP256K1.affine(self.p) + if p is None: + return None + if self.compressed: + return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big') + else: + return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big') - def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) + def verify_ecdsa(self, sig, msg, low_s=True): + """Verify a strictly DER-encoded ECDSA signature against this pubkey.""" + assert(self.valid) + if (sig[1] + 2 != len(sig)): + return False + if (len(sig) < 4): + return False + if (sig[0] != 0x30): + return False + if (sig[2] != 0x02): + return False + rlen = sig[3] + if (len(sig) < 6 + rlen): + return False + if rlen < 1 or rlen > 33: + return False + if sig[4] >= 0x80: + return False + if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)): + return False + r = int.from_bytes(sig[4:4+rlen], 'big') + if (sig[4+rlen] != 0x02): + return False + slen = sig[5+rlen] + if slen < 1 or slen > 33: + return False + if (len(sig) != 6 + rlen + slen): + return False + if sig[6+rlen] >= 0x80: + return False + if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)): + return False + s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big') + if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER: + return False + if low_s and s >= SECP256K1_ORDER_HALF: + return False + z = int.from_bytes(msg, 'big') + w = modinv(s, SECP256K1_ORDER) + u1 = z*w % SECP256K1_ORDER + u2 = r*w % SECP256K1_ORDER + R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)])) + if R is None or R[0] != r: + return False + return True +class ECKey(): + """A secp256k1 private key""" + + def __init__(self): + self.valid = False + + def set(self, secret, compressed): + """Construct a private key object with given 32-byte secret and compressed flag.""" + assert(len(secret) == 32) + secret = int.from_bytes(secret, 'big') + self.valid = (secret > 0 and secret < SECP256K1_ORDER) + if self.valid: + self.secret = secret + self.compressed = compressed + + def generate(self, compressed=True): + """Generate a random private key (compressed or uncompressed).""" + self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed) + + def get_bytes(self): + """Retrieve the 32-byte representation of this key.""" + assert(self.valid) + return self.secret.to_bytes(32, 'big') + + @property + def is_valid(self): + return self.valid + + @property + def is_compressed(self): + return self.compressed + + def get_pubkey(self): + """Compute an ECPubKey object for this secret key.""" + assert(self.valid) + ret = ECPubKey() + p = SECP256K1.mul([(SECP256K1_G, self.secret)]) + ret.p = p + ret.valid = True + ret.compressed = self.compressed + return ret + + def sign_ecdsa(self, msg, low_s=True): + """Construct a DER-encoded ECDSA signature with this key.""" + assert(self.valid) + z = int.from_bytes(msg, 'big') + # Note: no RFC6979, but a simple random nonce (some tests rely on distinct transactions for the same operation) + k = random.randrange(1, SECP256K1_ORDER) + R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)])) + r = R[0] % SECP256K1_ORDER + s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER + if low_s and s > SECP256K1_ORDER_HALF: + s = SECP256K1_ORDER - s + rb = r.to_bytes((r.bit_length() + 8) // 8, 'big') + sb = s.to_bytes((s.bit_length() + 8) // 8, 'big') + return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb diff --git a/test/lint/lint-python-dead-code.sh b/test/lint/lint-python-dead-code.sh index 863caa9d5c4..588ba428d75 100755 --- a/test/lint/lint-python-dead-code.sh +++ b/test/lint/lint-python-dead-code.sh @@ -15,5 +15,5 @@ fi vulture \ --min-confidence 60 \ - --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,get_ecdh_key,get_privkey,is_compressed,is_fullyvalid,msg_generic,on_*,optionxform,restype,set_privkey,profile_with_perf" \ + --ignore-names "argtypes,connection_lost,connection_made,converter,data_received,daemon,errcheck,is_compressed,is_valid,verify_ecdsa,msg_generic,on_*,optionxform,restype,profile_with_perf" \ $(git ls-files -- "*.py" ":(exclude)contrib/" ":(exclude)test/functional/data/invalid_txs.py") From e78007fc1a8f1891078d4f6bfe7583a77dda7ee9 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 15 May 2019 00:21:11 -0400 Subject: [PATCH 32/36] Make and get the multisig redeemscript and destination in one function instead of two Instead of creating a redeemScript with CreateMultisigRedeemscript and then getting the destination with AddAndGetDestinationForScript, do both in the same function. CreateMultisigRedeemscript is changed to AddAndGetMultisigDestination. It creates the redeemScript and returns it via an output parameter. Then it calls AddAndGetDestinationForScript to add the destination to the keystore and get the proper destination. This allows us to inspect the public keys in the redeemScript before creating the destination so that the correct destination is used when uncompressed pubkeys are in the multisig. Github-Pull: #16026 Rebased-From: a49503402b6bc21e3878e151c07529941d36aed0 --- src/rpc/misc.cpp | 4 ++-- src/rpc/util.cpp | 24 +++++++++++++++------ src/rpc/util.h | 3 ++- src/wallet/rpcwallet.cpp | 4 ++-- test/functional/rpc_createmultisig.py | 30 ++++++++++++++++++++++++++- 5 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 0a97f802974..780a10e03ff 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -128,9 +128,9 @@ static UniValue createmultisig(const JSONRPCRequest& request) } // Construct using pay-to-script-hash: - const CScript inner = CreateMultisigRedeemscript(required, pubkeys); CBasicKeyStore keystore; - const CTxDestination dest = AddAndGetDestinationForScript(keystore, inner, output_type); + CScript inner; + const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner); UniValue result(UniValue::VOBJ); result.pushKV("address", EncodeDestination(dest)); diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 40ac1331863..ff832810bcf 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -46,8 +47,8 @@ CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in) return vchPubKey; } -// Creates a multisig redeemscript from a given list of public keys and number required. -CScript CreateMultisigRedeemscript(const int required, const std::vector& pubkeys) +// Creates a multisig address from a given list of public keys, number of signatures required, and the address type +CTxDestination AddAndGetMultisigDestination(const int required, const std::vector& pubkeys, OutputType type, CKeyStore& keystore, CScript& script_out) { // Gather public keys if (required < 1) { @@ -60,13 +61,24 @@ CScript CreateMultisigRedeemscript(const int required, const std::vector 16\nReduce the number"); } - CScript result = GetScriptForMultisig(required, pubkeys); + script_out = GetScriptForMultisig(required, pubkeys); - if (result.size() > MAX_SCRIPT_ELEMENT_SIZE) { - throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE))); + if (script_out.size() > MAX_SCRIPT_ELEMENT_SIZE) { + throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", script_out.size(), MAX_SCRIPT_ELEMENT_SIZE))); } - return result; + // Check if any keys are uncompressed. If so, the type is legacy + for (const CPubKey& pk : pubkeys) { + if (!pk.IsCompressed()) { + type = OutputType::LEGACY; + break; + } + } + + // Make the address + CTxDestination dest = AddAndGetDestinationForScript(keystore, script_out, type); + + return dest; } class DescribeAddressVisitor : public boost::static_visitor diff --git a/src/rpc/util.h b/src/rpc/util.h index f1bd2c89dfc..d4d8694053a 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -6,6 +6,7 @@ #define BITCOIN_RPC_UTIL_H #include +#include #include #include #include