From 5669afb80e350027705dc378d2ab16232567511a Mon Sep 17 00:00:00 2001 From: sinetek Date: Fri, 26 Aug 2022 17:45:54 +0200 Subject: [PATCH 01/52] fs: drop old WSL1 hack. --- src/fs.cpp | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/fs.cpp b/src/fs.cpp index 74b167e313..bfc95879f5 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -60,36 +60,20 @@ FileLock::~FileLock() } } -static bool IsWSL() -{ - struct utsname uname_data; - return uname(&uname_data) == 0 && std::string(uname_data.version).find("Microsoft") != std::string::npos; -} - bool FileLock::TryLock() { if (fd == -1) { return false; } - // Exclusive file locking is broken on WSL using fcntl (issue #18622) - // This workaround can be removed once the bug on WSL is fixed - static const bool is_wsl = IsWSL(); - if (is_wsl) { - if (flock(fd, LOCK_EX | LOCK_NB) == -1) { - reason = GetErrorReason(); - return false; - } - } else { - struct flock lock; - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - if (fcntl(fd, F_SETLK, &lock) == -1) { - reason = GetErrorReason(); - return false; - } + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(fd, F_SETLK, &lock) == -1) { + reason = GetErrorReason(); + return false; } return true; From 9d3127b11e34131409dab7c08bde5b444d90b2cb Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Fri, 22 Apr 2022 19:15:17 -0400 Subject: [PATCH 02/52] Add settings.json prune-prev, proxy-prev, onion-prev settings This provides a way for the GUI settings dialog box to retain previous pruning and proxy settings when they are disabled, as requested by vasild: https://github.com/bitcoin/bitcoin/pull/15936#pullrequestreview-937685759 https://github.com/bitcoin/bitcoin/pull/15936#discussion_r850568749 https://github.com/bitcoin/bitcoin/pull/15936#discussion_r852998379 Importantly, while this PR changes the settings.json format, it changes it in a fully backwards compatible way, so previous versious of bitcoind and bitcoin-qt will correctly interpret prune, proxy, and onion settins written by new versions of bitcoin-qt. --- src/qt/optionsmodel.cpp | 130 +++++++++++++++++++++++----------------- src/qt/optionsmodel.h | 13 +--- 2 files changed, 77 insertions(+), 66 deletions(-) diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 0b4359a917..cf35dbfcb0 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -59,7 +59,7 @@ static const char* SettingName(OptionsModel::OptionID option) } /** Call node.updateRwSetting() with Bitcoin 22.x workaround. */ -static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const util::SettingsValue& value) +static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID option, const std::string& suffix, const util::SettingsValue& value) { if (value.isNum() && (option == OptionsModel::DatabaseCache || @@ -73,9 +73,9 @@ static void UpdateRwSetting(interfaces::Node& node, OptionsModel::OptionID optio // in later releases by https://github.com/bitcoin/bitcoin/pull/24498. // If new numeric settings are added, they can be written as numbers // instead of strings, because bitcoin 22.x will not try to read these. - node.updateRwSetting(SettingName(option), value.getValStr()); + node.updateRwSetting(SettingName(option) + suffix, value.getValStr()); } else { - node.updateRwSetting(SettingName(option), value); + node.updateRwSetting(SettingName(option) + suffix, value); } } @@ -131,13 +131,6 @@ void OptionsModel::addOverriddenOption(const std::string &option) bool OptionsModel::Init(bilingual_str& error) { // Initialize display settings from stored settings. - m_prune_size_gb = PruneSizeGB(node().getPersistentSetting("prune")); - ProxySetting proxy = ParseProxyString(SettingToString(node().getPersistentSetting("proxy"), GetDefaultProxyAddress().toStdString())); - m_proxy_ip = proxy.ip; - m_proxy_port = proxy.port; - ProxySetting onion = ParseProxyString(SettingToString(node().getPersistentSetting("onion"), GetDefaultProxyAddress().toStdString())); - m_onion_ip = onion.ip; - m_onion_port = onion.port; language = QString::fromStdString(SettingToString(node().getPersistentSetting("lang"), "")); checkAndMigrate(); @@ -318,8 +311,6 @@ void OptionsModel::SetPruneTargetGB(int prune_target_gb) const util::SettingsValue cur_value = node().getPersistentSetting("prune"); const util::SettingsValue new_value = PruneSetting(prune_target_gb > 0, prune_target_gb); - m_prune_size_gb = prune_target_gb; - // Force setting to take effect. It is still safe to change the value at // this point because this function is only called after the intro screen is // shown, before the node starts. @@ -332,7 +323,12 @@ void OptionsModel::SetPruneTargetGB(int prune_target_gb) PruneSizeGB(cur_value) != PruneSizeGB(new_value)) { // Call UpdateRwSetting() instead of setOption() to avoid setting // RestartRequired flag - UpdateRwSetting(node(), Prune, new_value); + UpdateRwSetting(node(), Prune, "", new_value); + } + + // Keep previous pruning size, if pruning was disabled. + if (PruneEnabled(cur_value)) { + UpdateRwSetting(node(), Prune, "-prev", PruneEnabled(new_value) ? util::SettingsValue{} : cur_value); } } @@ -360,9 +356,9 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in return successful; } -QVariant OptionsModel::getOption(OptionID option) const +QVariant OptionsModel::getOption(OptionID option, const std::string& suffix) const { - auto setting = [&]{ return node().getPersistentSetting(SettingName(option)); }; + auto setting = [&]{ return node().getPersistentSetting(SettingName(option) + suffix); }; QSettings settings; switch (option) { @@ -389,19 +385,30 @@ QVariant OptionsModel::getOption(OptionID option) const // default proxy case ProxyUse: + case ProxyUseTor: return ParseProxyString(SettingToString(setting(), "")).is_set; case ProxyIP: - return m_proxy_ip; + case ProxyIPTor: { + ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); + if (proxy.is_set) { + return proxy.ip; + } else if (suffix.empty()) { + return getOption(option, "-prev"); + } else { + return ParseProxyString(GetDefaultProxyAddress().toStdString()).ip; + } + } case ProxyPort: - return m_proxy_port; - - // separate Tor proxy - case ProxyUseTor: - return ParseProxyString(SettingToString(setting(), "")).is_set; - case ProxyIPTor: - return m_onion_ip; - case ProxyPortTor: - return m_onion_port; + case ProxyPortTor: { + ProxySetting proxy = ParseProxyString(SettingToString(setting(), "")); + if (proxy.is_set) { + return proxy.port; + } else if (suffix.empty()) { + return getOption(option, "-prev"); + } else { + return ParseProxyString(GetDefaultProxyAddress().toStdString()).port; + } + } #ifdef ENABLE_WALLET case SpendZeroConfChange: @@ -426,7 +433,9 @@ QVariant OptionsModel::getOption(OptionID option) const case Prune: return PruneEnabled(setting()); case PruneSize: - return m_prune_size_gb; + return PruneEnabled(setting()) ? PruneSizeGB(setting()) : + suffix.empty() ? getOption(option, "-prev") : + DEFAULT_PRUNE_TARGET_GB; case DatabaseCache: return qlonglong(SettingToInt(setting(), nDefaultDbCache)); case ThreadsScriptVerif: @@ -440,10 +449,10 @@ QVariant OptionsModel::getOption(OptionID option) const } } -bool OptionsModel::setOption(OptionID option, const QVariant& value) +bool OptionsModel::setOption(OptionID option, const QVariant& value, const std::string& suffix) { - auto changed = [&] { return value.isValid() && value != getOption(option); }; - auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, value); }; + auto changed = [&] { return value.isValid() && value != getOption(option, suffix); }; + auto update = [&](const util::SettingsValue& value) { return UpdateRwSetting(node(), option, suffix, value); }; bool successful = true; /* set to false on parse error */ QSettings settings; @@ -481,52 +490,60 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) // default proxy case ProxyUse: if (changed()) { - update(ProxyString(value.toBool(), m_proxy_ip, m_proxy_port)); - setRestartRequired(true); + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(ProxyString(value.toBool(), getOption(ProxyIP).toString(), getOption(ProxyPort).toString())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); } break; case ProxyIP: if (changed()) { - m_proxy_ip = value.toString(); - if (getOption(ProxyUse).toBool()) { - update(ProxyString(true, m_proxy_ip, m_proxy_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUse).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, value.toString(), getOption(ProxyPort).toString())); } + if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); } break; case ProxyPort: if (changed()) { - m_proxy_port = value.toString(); - if (getOption(ProxyUse).toBool()) { - update(ProxyString(true, m_proxy_ip, m_proxy_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUse).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, getOption(ProxyIP).toString(), value.toString())); } + if (suffix.empty() && getOption(ProxyUse).toBool()) setRestartRequired(true); } break; // separate Tor proxy case ProxyUseTor: if (changed()) { - update(ProxyString(value.toBool(), m_onion_ip, m_onion_port)); - setRestartRequired(true); + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(ProxyString(value.toBool(), getOption(ProxyIPTor).toString(), getOption(ProxyPortTor).toString())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); } break; case ProxyIPTor: if (changed()) { - m_onion_ip = value.toString(); - if (getOption(ProxyUseTor).toBool()) { - update(ProxyString(true, m_onion_ip, m_onion_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, value.toString(), getOption(ProxyPortTor).toString())); } + if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); } break; case ProxyPortTor: if (changed()) { - m_onion_port = value.toString(); - if (getOption(ProxyUseTor).toBool()) { - update(ProxyString(true, m_onion_ip, m_onion_port)); - setRestartRequired(true); + if (suffix.empty() && !getOption(ProxyUseTor).toBool()) { + setOption(option, value, "-prev"); + } else { + update(ProxyString(true, getOption(ProxyIPTor).toString(), value.toString())); } + if (suffix.empty() && getOption(ProxyUseTor).toBool()) setRestartRequired(true); } break; @@ -580,17 +597,20 @@ bool OptionsModel::setOption(OptionID option, const QVariant& value) break; case Prune: if (changed()) { - update(PruneSetting(value.toBool(), m_prune_size_gb)); - setRestartRequired(true); + if (suffix.empty() && !value.toBool()) setOption(option, true, "-prev"); + update(PruneSetting(value.toBool(), getOption(PruneSize).toInt())); + if (suffix.empty() && value.toBool()) UpdateRwSetting(node(), option, "-prev", {}); + if (suffix.empty()) setRestartRequired(true); } break; case PruneSize: if (changed()) { - m_prune_size_gb = ParsePruneSizeGB(value); - if (getOption(Prune).toBool()) { - update(PruneSetting(true, m_prune_size_gb)); - setRestartRequired(true); + if (suffix.empty() && !getOption(Prune).toBool()) { + setOption(option, value, "-prev"); + } else { + update(PruneSetting(true, ParsePruneSizeGB(value))); } + if (suffix.empty() && getOption(Prune).toBool()) setRestartRequired(true); } break; case DatabaseCache: diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 42b89c5029..0020b89e21 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -81,8 +81,8 @@ class OptionsModel : public QAbstractListModel int rowCount(const QModelIndex & parent = QModelIndex()) const override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; - QVariant getOption(OptionID option) const; - bool setOption(OptionID option, const QVariant& value); + QVariant getOption(OptionID option, const std::string& suffix="") const; + bool setOption(OptionID option, const QVariant& value, const std::string& suffix=""); /** Updates current unit in memory, settings and emits displayUnitChanged(new_unit) signal */ void setDisplayUnit(const QVariant& new_unit); @@ -121,15 +121,6 @@ class OptionsModel : public QAbstractListModel bool m_sub_fee_from_amount; bool m_enable_psbt_controls; - //! In-memory settings for display. These are stored persistently by the - //! bitcoin node but it's also nice to store them in memory to prevent them - //! getting cleared when enable/disable toggles are used in the GUI. - int m_prune_size_gb; - QString m_proxy_ip; - QString m_proxy_port; - QString m_onion_ip; - QString m_onion_port; - /* settings that were overridden by command-line */ QString strOverriddenByCommandLine; From 741908afc1f9ed2040c18667c75665b300c5dfe7 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Sun, 27 Nov 2022 23:46:15 +0100 Subject: [PATCH 03/52] test: previous releases: add v24.0.1 --- test/functional/wallet_backwards_compatibility.py | 4 +++- test/get_previous_releases.py | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/test/functional/wallet_backwards_compatibility.py b/test/functional/wallet_backwards_compatibility.py index c9cb3285fb..e1797262cc 100755 --- a/test/functional/wallet_backwards_compatibility.py +++ b/test/functional/wallet_backwards_compatibility.py @@ -33,11 +33,12 @@ def add_options(self, parser): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 10 + self.num_nodes = 11 # Add new version after each release: self.extra_args = [ ["-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to mine blocks. noban for immediate tx relay ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to receive coins, swap wallets, etc + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v24.0.1 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v23.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v22.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.21.0 @@ -57,6 +58,7 @@ def setup_nodes(self): self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[ None, None, + 240001, 230000, 220000, 210000, diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py index ca06d7b8f8..ca1b39d044 100755 --- a/test/get_previous_releases.py +++ b/test/get_previous_releases.py @@ -80,6 +80,15 @@ "078f96b1e92895009c798ab827fb3fde5f6719eee886bd0c0e93acab18ea4865": {"tag": "v23.0", "tarball": "bitcoin-23.0-riscv64-linux-gnu.tar.gz"}, "c816780583009a9dad426dc0c183c89be9da98906e1e2c7ebae91041c1aaaaf3": {"tag": "v23.0", "tarball": "bitcoin-23.0-x86_64-apple-darwin.tar.gz"}, "2cca490c1f2842884a3c5b0606f179f9f937177da4eadd628e3f7fd7e25d26d0": {"tag": "v23.0", "tarball": "bitcoin-23.0-x86_64-linux-gnu.tar.gz"}, + + "0b48b9e69b30037b41a1e6b78fb7cbcc48c7ad627908c99686e81f3802454609": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-aarch64-linux-gnu.tar.gz"}, + "37d7660f0277301744e96426bbb001d2206b8d4505385dfdeedf50c09aaaef60": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-arm-linux-gnueabihf.tar.gz"}, + "90ed59e86bfda1256f4b4cad8cc1dd77ee0efec2492bcb5af61402709288b62c": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-arm64-apple-darwin.tar.gz"}, + "7590645e8676f8b5fda62dc20174474c4ac8fd0defc83a19ed908ebf2e94dc11": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-powerpc64-linux-gnu.tar.gz"}, + "79e89a101f23ff87816675b98769cd1ee91059f95c5277f38f48f21a9f7f8509": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-powerpc64le-linux-gnu.tar.gz"}, + "6b163cef7de4beb07b8cb3347095e0d76a584019b1891135cd1268a1f05b9d88": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-riscv64-linux-gnu.tar.gz"}, + "e2f751512f3c0f00eb68ba946d9c829e6cf99422a61e8f5e0a7c109c318674d0": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-x86_64-apple-darwin.tar.gz"}, + "49df6e444515d457ea0b885d66f521f2a26ca92ccf73d5296082e633544253bf": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-x86_64-linux-gnu.tar.gz"}, } From 7a83aa0982dbe1c5e89b904f636840cb2ef56357 Mon Sep 17 00:00:00 2001 From: brunoerg Date: Fri, 16 Dec 2022 14:45:00 -0300 Subject: [PATCH 04/52] test: add coverage for unparsable `-maxuploadtarget` --- test/functional/feature_maxuploadtarget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index 3ea412002a..b08c5f9589 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -164,6 +164,9 @@ def run_test(self): assert_equal(len(peer_info), 1) # node is still connected assert_equal(peer_info[0]['permissions'], ['download']) + self.log.info("Test passing an unparsable value to -maxuploadtarget throws an error") + self.stop_node(0) + self.nodes[0].assert_start_raises_init_error(extra_args=["-maxuploadtarget=abc"], expected_msg="Error: Unable to parse -maxuploadtarget: 'abc'") if __name__ == '__main__': MaxUploadTest().main() From 691eaf8873fe2f189153ca637506a0291504c97a Mon Sep 17 00:00:00 2001 From: Matt Whitlock Date: Fri, 23 Feb 2018 14:02:37 -0500 Subject: [PATCH 05/52] Pass MSG_MORE flag when sending non-final network messages Since Nagle's algorithm is disabled, each and every call to send(2) can potentially generate a separate TCP segment on the wire. This is especially inefficient when sending the tiny header preceding each message payload. Linux implements a MSG_MORE flag that tells the kernel not to push the passed data immediately to the connected peer but rather to collect it in the socket's internal transmit buffer where it can be combined with data from successive calls to send(2). Where available, specify this flag when calling send(2) in CConnman::SocketSendData(CNode &) if the data buffer being sent is not the last one in node.vSendMsg. --- src/net.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/net.cpp b/src/net.cpp index d94542887b..9db4b9c9e6 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -802,7 +802,13 @@ size_t CConnman::SocketSendData(CNode& node) const if (!node.m_sock) { break; } - nBytes = node.m_sock->Send(reinterpret_cast(data.data()) + node.nSendOffset, data.size() - node.nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT); + int flags = MSG_NOSIGNAL | MSG_DONTWAIT; +#ifdef MSG_MORE + if (it + 1 != node.vSendMsg.end()) { + flags |= MSG_MORE; + } +#endif + nBytes = node.m_sock->Send(reinterpret_cast(data.data()) + node.nSendOffset, data.size() - node.nSendOffset, flags); } if (nBytes > 0) { node.m_last_send = GetTime(); From a804f3cfc0b4761b9ca7976e6e4472cd93599bbf Mon Sep 17 00:00:00 2001 From: Seibart Nedor Date: Tue, 14 Jun 2022 19:43:59 +0300 Subject: [PATCH 06/52] wallet: extract and reuse RPC argument format definition for outputs --- src/wallet/rpc/spend.cpp | 48 +++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 74f11a71b7..0c2994706e 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -957,6 +957,26 @@ RPCHelpMan signrawtransactionwithwallet() }; } +// Definition of allowed formats of specifying transaction outputs in +// `send` and `walletcreatefundedpsbt` RPCs. +static std::vector OutputsDoc() +{ + return + { + {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", + { + {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address,\n" + "the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, + }, + }, + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", + { + {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, + }, + }, + }; +} + static RPCHelpMan bumpfee_helper(std::string method_name) { const bool want_psbt = method_name == "psbtbumpfee"; @@ -1145,18 +1165,7 @@ RPCHelpMan send() {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs (key-value pairs), where none of the keys are duplicated.\n" "That is, each address can only appear once and there can only be one 'data' object.\n" "For convenience, a dictionary, which holds the key-value pairs directly, is also accepted.", - { - {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", - { - {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, - }, - }, - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, - }, - }, - }, + OutputsDoc(), RPCArgOptions{.skip_type_check = true}}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" @@ -1607,19 +1616,8 @@ RPCHelpMan walletcreatefundedpsbt() "That is, each address can only appear once and there can only be one 'data' object.\n" "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" "accepted as second parameter.", - { - {"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "", - { - {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, - }, - }, - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, - }, - }, - }, - RPCArgOptions{.skip_type_check = true}}, + OutputsDoc(), + RPCArgOptions{.skip_type_check = true}}, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", Cat>( From c0ebb9838252fb187db8719755801758d89651f7 Mon Sep 17 00:00:00 2001 From: Seibart Nedor Date: Sat, 11 Jun 2022 21:54:52 +0300 Subject: [PATCH 07/52] wallet: add `outputs` arguments to `bumpfee` and `psbtbumpfee` --- src/rpc/rawtransaction_util.cpp | 43 ++++++++++++++++++++------------- src/rpc/rawtransaction_util.h | 7 ++++++ src/wallet/feebumper.cpp | 21 ++++++++++------ src/wallet/feebumper.h | 3 ++- src/wallet/interfaces.cpp | 3 ++- src/wallet/rpc/spend.cpp | 23 +++++++++++++++--- 6 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 15b8e1dcd0..3ba930f84f 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -21,12 +21,8 @@ #include #include -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf) +void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, std::optional rbf) { - if (outputs_in.isNull()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); - } - UniValue inputs; if (inputs_in.isNull()) { inputs = UniValue::VARR; @@ -34,18 +30,6 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal inputs = inputs_in.get_array(); } - const bool outputs_is_obj = outputs_in.isObject(); - UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); - - CMutableTransaction rawTx; - - if (!locktime.isNull()) { - int64_t nLockTime = locktime.getInt(); - if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); - rawTx.nLockTime = nLockTime; - } - for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; const UniValue& o = input.get_obj(); @@ -84,6 +68,16 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.vin.push_back(in); } +} + +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in) +{ + if (outputs_in.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); + } + + const bool outputs_is_obj = outputs_in.isObject(); + UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); if (!outputs_is_obj) { // Translate array of key-value pairs into dict @@ -132,6 +126,21 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal rawTx.vout.push_back(out); } } +} + +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf) +{ + CMutableTransaction rawTx; + + if (!locktime.isNull()) { + int64_t nLockTime = locktime.getInt(); + if (nLockTime < 0 || nLockTime > LOCKTIME_MAX) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); + rawTx.nLockTime = nLockTime; + } + + AddInputs(rawTx, inputs_in, rbf); + AddOutputs(rawTx, outputs_in); if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 0c3823bc1e..a863432b7a 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -38,6 +38,13 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const */ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map& coins); + +/** Normalize univalue-represented inputs and add them to the transaction */ +void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, bool rbf); + +/** Normalize univalue-represented outputs and add them to the transaction */ +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in); + /** Create a transaction from univalue parameters */ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf); diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index bd158b5985..37a704bfa4 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -155,7 +155,7 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid) } Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector& errors, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine) + CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, bool require_mine, const std::vector& outputs) { // We are going to modify coin control later, copy to re-use CCoinControl new_coin_control(coin_control); @@ -222,11 +222,19 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo return result; } - // Fill in recipients(and preserve a single change key if there is one) - // While we're here, calculate the output amount - std::vector recipients; + // Calculate the old output amount. CAmount output_value = 0; - for (const auto& output : wtx.tx->vout) { + for (const auto& old_output : wtx.tx->vout) { + output_value += old_output.nValue; + } + + old_fee = input_value - output_value; + + // Fill in recipients (and preserve a single change key if there + // is one). If outputs vector is non-empty, replace original + // outputs with its contents, otherwise use original outputs. + std::vector recipients; + for (const auto& output : outputs.empty() ? wtx.tx->vout : outputs) { if (!OutputIsChange(wallet, output)) { CRecipient recipient = {output.scriptPubKey, output.nValue, false}; recipients.push_back(recipient); @@ -235,11 +243,8 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo ExtractDestination(output.scriptPubKey, change_dest); new_coin_control.destChange = change_dest; } - output_value += output.nValue; } - old_fee = input_value - output_value; - if (coin_control.m_feerate) { // The user provided a feeRate argument. // We calculate this here to avoid compiler warning on the cs_wallet lock diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h index a96871b26f..53cf16e0f1 100644 --- a/src/wallet/feebumper.h +++ b/src/wallet/feebumper.h @@ -51,7 +51,8 @@ Result CreateRateBumpTransaction(CWallet& wallet, CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx, - bool require_mine); + bool require_mine, + const std::vector& outputs); //! Sign the new transaction, //! @return false if the tx couldn't be found or if it was diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 68dd3da9b5..1a76e46c54 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -291,7 +291,8 @@ class WalletImpl : public Wallet CAmount& new_fee, CMutableTransaction& mtx) override { - return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx, /* require_mine= */ true) == feebumper::Result::OK; + std::vector outputs; // just an empty list of new recipients for now + return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx, /* require_mine= */ true, outputs) == feebumper::Result::OK; } bool signBumpTransaction(CMutableTransaction& mtx) override { return feebumper::SignTransaction(*m_wallet.get(), mtx); } bool commitBumpTransaction(const uint256& txid, diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 0c2994706e..860b1459a5 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -958,7 +958,7 @@ RPCHelpMan signrawtransactionwithwallet() } // Definition of allowed formats of specifying transaction outputs in -// `send` and `walletcreatefundedpsbt` RPCs. +// `bumpfee`, `psbtbumpfee`, `send` and `walletcreatefundedpsbt` RPCs. static std::vector OutputsDoc() { return @@ -1013,7 +1013,12 @@ static RPCHelpMan bumpfee_helper(std::string method_name) "still be replaceable in practice, for example if it has unconfirmed ancestors which\n" "are replaceable).\n"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, + "\"" + FeeModes("\"\n\"") + "\""}, + {"outputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "New outputs (key-value pairs) which will replace\n" + "the original ones, if provided. Each address can only appear once and there can\n" + "only be one \"data\" object.\n", + OutputsDoc(), + RPCArgOptions{.skip_type_check = true}}, }, RPCArgOptions{.oneline_description="options"}}, }, @@ -1050,6 +1055,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) coin_control.fAllowWatchOnly = pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); // optional parameters coin_control.m_signal_bip125_rbf = true; + std::vector outputs; if (!request.params[1].isNull()) { UniValue options = request.params[1]; @@ -1060,6 +1066,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) {"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode() {"replaceable", UniValueType(UniValue::VBOOL)}, {"estimate_mode", UniValueType(UniValue::VSTR)}, + {"outputs", UniValueType()}, // will be checked by AddOutputs() }, true, true); @@ -1073,6 +1080,16 @@ static RPCHelpMan bumpfee_helper(std::string method_name) coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool(); } SetFeeEstimateMode(*pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /*override_min_fee=*/false); + + // Prepare new outputs by creating a temporary tx and calling AddOutputs(). + if (!options["outputs"].isNull()) { + if (options["outputs"].isArray() && options["outputs"].empty()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument cannot be an empty array"); + } + CMutableTransaction tempTx; + AddOutputs(tempTx, options["outputs"]); + outputs = tempTx.vout; + } } // Make sure the results are valid at least up to the most recent block @@ -1090,7 +1107,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) CMutableTransaction mtx; feebumper::Result res; // Targeting feerate bump. - res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt); + res = feebumper::CreateRateBumpTransaction(*pwallet, hash, coin_control, errors, old_fee, new_fee, mtx, /*require_mine=*/ !want_psbt, outputs); if (res != feebumper::Result::OK) { switch(res) { case feebumper::Result::INVALID_ADDRESS_OR_KEY: From 4c8ecccdcd813fac3a7ef6a1493ef3977220421d Mon Sep 17 00:00:00 2001 From: Seibart Nedor Date: Sun, 12 Jun 2022 01:52:24 +0300 Subject: [PATCH 08/52] test: add tests for `outputs` argument to `bumpfee`/`psbtbumpfee` --- test/functional/wallet_bumpfee.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index a2ae997ecb..ad79e0288c 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -81,7 +81,7 @@ def run_test(self): self.log.info("Running tests") dest_address = peer_node.getnewaddress() - for mode in ["default", "fee_rate"]: + for mode in ["default", "fee_rate", "new_outputs"]: test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address) self.test_invalid_parameters(rbf_node, peer_node, dest_address) test_segwit_bumpfee_succeeds(self, rbf_node, dest_address) @@ -157,6 +157,14 @@ def test_invalid_parameters(self, rbf_node, peer_node, dest_address): assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', rbf_node.bumpfee, rbfid, {"estimate_mode": mode}) + self.log.info("Test invalid outputs values") + assert_raises_rpc_error(-8, "Invalid parameter, output argument cannot be an empty array", + rbf_node.bumpfee, rbfid, {"outputs": []}) + assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: " + dest_address, + rbf_node.bumpfee, rbfid, {"outputs": [{dest_address: 0.1}, {dest_address: 0.2}]}) + assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", + rbf_node.bumpfee, rbfid, {"outputs": [{"data": "deadbeef"}, {"data": "deadbeef"}]}) + self.clear_mempool() @@ -169,6 +177,10 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address): if mode == "fee_rate": bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"fee_rate": str(NORMAL)}) bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": NORMAL}) + elif mode == "new_outputs": + new_address = peer_node.getnewaddress() + bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"outputs": {new_address: 0.0003}}) + bumped_tx = rbf_node.bumpfee(rbfid, {"outputs": {new_address: 0.0003}}) else: bumped_psbt = rbf_node.psbtbumpfee(rbfid) bumped_tx = rbf_node.bumpfee(rbfid) @@ -192,6 +204,10 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address): bumpedwtx = rbf_node.gettransaction(bumped_tx["txid"]) assert_equal(oldwtx["replaced_by_txid"], bumped_tx["txid"]) assert_equal(bumpedwtx["replaces_txid"], rbfid) + # if this is a new_outputs test, check that outputs were indeed replaced + if mode == "new_outputs": + assert len(bumpedwtx["details"]) == 1 + assert bumpedwtx["details"][0]["address"] == new_address self.clear_mempool() @@ -628,12 +644,14 @@ def get_change_address(tx): self.clear_mempool() -def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): +def spend_one_input(node, dest_address, change_size=Decimal("0.00049000"), data=None): tx_input = dict( sequence=MAX_BIP125_RBF_SEQUENCE, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) destinations = {dest_address: Decimal("0.00050000")} if change_size > 0: destinations[node.getrawchangeaddress()] = change_size + if data: + destinations['data'] = data rawtx = node.createrawtransaction([tx_input], destinations) signedtx = node.signrawtransactionwithwallet(rawtx) txid = node.sendrawtransaction(signedtx["hex"]) From ab4efad51b9ba276ffeb6871931e13772493f7cc Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Thu, 26 Jan 2023 02:56:34 +0100 Subject: [PATCH 09/52] test: fix immediate tx relay in wallet_groups.py --- test/functional/wallet_groups.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index 83c1826a41..bdb9081261 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -41,6 +41,11 @@ def skip_test_if_missing_module(self): def run_test(self): self.log.info("Setting up") + # To take full use of immediate tx relay, all nodes need to be reachable + # via inbound peers, i.e. connect first to last to close the circle + # (the default test network topology looks like this: + # node0 <-- node1 <-- node2 <-- node3 <-- node4 <-- node5) + self.connect_nodes(0, self.num_nodes - 1) # Mine some coins self.generate(self.nodes[0], COINBASE_MATURITY + 1) From 906631450df9927e690658184aa8a6b1b3a29ee9 Mon Sep 17 00:00:00 2001 From: Greg Sanders Date: Mon, 30 Jan 2023 10:46:15 -0500 Subject: [PATCH 10/52] s/transcation/transaction/ --- src/psbt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psbt.h b/src/psbt.h index 8fafadcea9..07c9a9c72e 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -1164,7 +1164,7 @@ struct PartiallySignedTransaction // Make sure that we got an unsigned tx if (!tx) { - throw std::ios_base::failure("No unsigned transcation was provided"); + throw std::ios_base::failure("No unsigned transaction was provided"); } // Read input data From e37bcaa0a6dbb334ab6e817efcb609ccee6edc39 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 21 Sep 2022 16:36:57 -0400 Subject: [PATCH 11/52] Split ChaCha20 into aligned/unaligned variants --- src/crypto/chacha20.cpp | 86 +++++++++++++++++++++++------------------ src/crypto/chacha20.h | 56 +++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 45 deletions(-) diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index 25d7baa8cc..cdeeee192e 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -8,6 +8,7 @@ #include #include +#include #include constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | (v >> (32 - c)); } @@ -23,7 +24,7 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | ( static const unsigned char sigma[] = "expand 32-byte k"; static const unsigned char tau[] = "expand 16-byte k"; -void ChaCha20::SetKey(const unsigned char* k, size_t keylen) +void ChaCha20Aligned::SetKey(const unsigned char* k, size_t keylen) { const unsigned char *constants; @@ -51,37 +52,34 @@ void ChaCha20::SetKey(const unsigned char* k, size_t keylen) input[15] = 0; } -ChaCha20::ChaCha20() +ChaCha20Aligned::ChaCha20Aligned() { memset(input, 0, sizeof(input)); } -ChaCha20::ChaCha20(const unsigned char* k, size_t keylen) +ChaCha20Aligned::ChaCha20Aligned(const unsigned char* k, size_t keylen) { SetKey(k, keylen); } -void ChaCha20::SetIV(uint64_t iv) +void ChaCha20Aligned::SetIV(uint64_t iv) { input[14] = iv; input[15] = iv >> 32; } -void ChaCha20::Seek(uint64_t pos) +void ChaCha20Aligned::Seek(uint64_t pos) { input[12] = pos; input[13] = pos >> 32; } -void ChaCha20::Keystream(unsigned char* c, size_t bytes) +inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks) { uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; - unsigned char *ctarget = nullptr; - unsigned char tmp[64]; - unsigned int i; - if (!bytes) return; + if (!blocks) return; j0 = input[0]; j1 = input[1]; @@ -101,10 +99,6 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes) j15 = input[15]; for (;;) { - if (bytes < 64) { - ctarget = c; - c = tmp; - } x0 = j0; x1 = j1; x2 = j2; @@ -171,28 +165,22 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes) WriteLE32(c + 56, x14); WriteLE32(c + 60, x15); - if (bytes <= 64) { - if (bytes < 64) { - for (i = 0;i < bytes;++i) ctarget[i] = c[i]; - } + if (blocks == 1) { input[12] = j12; input[13] = j13; return; } - bytes -= 64; + blocks -= 1; c += 64; } } -void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) +inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, size_t blocks) { uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; - unsigned char *ctarget = nullptr; - unsigned char tmp[64]; - unsigned int i; - if (!bytes) return; + if (!blocks) return; j0 = input[0]; j1 = input[1]; @@ -212,14 +200,6 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) j15 = input[15]; for (;;) { - if (bytes < 64) { - // if m has fewer than 64 bytes available, copy m to tmp and - // read from tmp instead - for (i = 0;i < bytes;++i) tmp[i] = m[i]; - m = tmp; - ctarget = c; - c = tmp; - } x0 = j0; x1 = j1; x2 = j2; @@ -303,16 +283,48 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) WriteLE32(c + 56, x14); WriteLE32(c + 60, x15); - if (bytes <= 64) { - if (bytes < 64) { - for (i = 0;i < bytes;++i) ctarget[i] = c[i]; - } + if (blocks == 1) { input[12] = j12; input[13] = j13; return; } - bytes -= 64; + blocks -= 1; c += 64; m += 64; } } + +void ChaCha20::Keystream(unsigned char* c, size_t bytes) +{ + if (!bytes) return; + if (bytes >= 64) { + size_t blocks = bytes / 64; + m_aligned.Keystream64(c, blocks); + c += blocks * 64; + bytes -= blocks * 64; + } + if (bytes) { + unsigned char buffer[64]; + m_aligned.Keystream64(buffer, 1); + memcpy(c, buffer, bytes); + } +} + +void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) +{ + if (!bytes) return; + if (bytes >= 64) { + size_t blocks = bytes / 64; + m_aligned.Crypt64(m, c, blocks); + c += blocks * 64; + m += blocks * 64; + bytes -= blocks * 64; + } + if (bytes) { + unsigned char buffer[64]; + m_aligned.Keystream64(buffer, 1); + for (unsigned i = 0; i < bytes; i++) { + c[i] = m[i] ^ buffer[i]; + } + } +} diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 624c083191..12ddef9256 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -8,19 +8,59 @@ #include #include -/** A class for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein - https://cr.yp.to/chacha/chacha-20080128.pdf */ -class ChaCha20 +// classes for ChaCha20 256-bit stream cipher developed by Daniel J. Bernstein +// https://cr.yp.to/chacha/chacha-20080128.pdf */ + +/** ChaCha20 cipher that only operates on multiples of 64 bytes. */ +class ChaCha20Aligned { private: uint32_t input[16]; public: - ChaCha20(); - ChaCha20(const unsigned char* key, size_t keylen); - void SetKey(const unsigned char* key, size_t keylen); //!< set key with flexible keylength; 256bit recommended */ - void SetIV(uint64_t iv); // set the 64bit nonce - void Seek(uint64_t pos); // set the 64bit block counter + ChaCha20Aligned(); + + /** Initialize a cipher with specified key (see SetKey for arguments). */ + ChaCha20Aligned(const unsigned char* key, size_t keylen); + + /** set key with flexible keylength (16 or 32 bytes; 32 recommended). */ + void SetKey(const unsigned char* key, size_t keylen); + + /** set the 64-bit nonce. */ + void SetIV(uint64_t iv); + + /** set the 64bit block counter (pos seeks to byte position 64*pos). */ + void Seek(uint64_t pos); + + /** outputs the keystream of size <64*blocks> into */ + void Keystream64(unsigned char* c, size_t blocks); + + /** enciphers the message of length <64*blocks> and write the enciphered representation into + * Used for encryption and decryption (XOR) + */ + void Crypt64(const unsigned char* input, unsigned char* output, size_t blocks); +}; + +/** Unrestricted ChaCha20 cipher. Seeks forward to a multiple of 64 bytes after every operation. */ +class ChaCha20 +{ +private: + ChaCha20Aligned m_aligned; + +public: + ChaCha20() = default; + + /** Initialize a cipher with specified key (see SetKey for arguments). */ + ChaCha20(const unsigned char* key, size_t keylen) : m_aligned(key, keylen) {} + + /** set key with flexible keylength (16 or 32 bytes; 32 recommended). */ + void SetKey(const unsigned char* key, size_t keylen) { m_aligned.SetKey(key, keylen); } + + /** set the 64-bit nonce. */ + void SetIV(uint64_t iv) { m_aligned.SetIV(iv); } + + /** set the 64bit block counter (pos seeks to byte position 64*pos). */ + void Seek(uint64_t pos) { m_aligned.Seek(pos); } /** outputs the keystream of size into */ void Keystream(unsigned char* c, size_t bytes); From 6babf402130a8f3ef3058594750aeaa50b8f5044 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 21 Sep 2022 16:58:13 -0400 Subject: [PATCH 12/52] Rename ChaCha20::Seek -> Seek64 to clarify multiple of 64 --- src/bench/chacha20.cpp | 2 +- src/crypto/chacha20.cpp | 2 +- src/crypto/chacha20.h | 4 ++-- src/crypto/chacha_poly_aead.cpp | 8 ++++---- src/test/crypto_tests.cpp | 8 ++++---- src/test/fuzz/crypto_chacha20.cpp | 2 +- src/test/fuzz/crypto_diff_fuzz_chacha20.cpp | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/bench/chacha20.cpp b/src/bench/chacha20.cpp index 656fb833e7..8d1d1952bc 100644 --- a/src/bench/chacha20.cpp +++ b/src/bench/chacha20.cpp @@ -16,7 +16,7 @@ static void CHACHA20(benchmark::Bench& bench, size_t buffersize) std::vector key(32,0); ChaCha20 ctx(key.data(), key.size()); ctx.SetIV(0); - ctx.Seek(0); + ctx.Seek64(0); std::vector in(buffersize,0); std::vector out(buffersize,0); bench.batch(in.size()).unit("byte").run([&] { diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index cdeeee192e..c72ccccc65 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -68,7 +68,7 @@ void ChaCha20Aligned::SetIV(uint64_t iv) input[15] = iv >> 32; } -void ChaCha20Aligned::Seek(uint64_t pos) +void ChaCha20Aligned::Seek64(uint64_t pos) { input[12] = pos; input[13] = pos >> 32; diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 12ddef9256..fdef257ed3 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -30,7 +30,7 @@ class ChaCha20Aligned void SetIV(uint64_t iv); /** set the 64bit block counter (pos seeks to byte position 64*pos). */ - void Seek(uint64_t pos); + void Seek64(uint64_t pos); /** outputs the keystream of size <64*blocks> into */ void Keystream64(unsigned char* c, size_t blocks); @@ -60,7 +60,7 @@ class ChaCha20 void SetIV(uint64_t iv) { m_aligned.SetIV(iv); } /** set the 64bit block counter (pos seeks to byte position 64*pos). */ - void Seek(uint64_t pos) { m_aligned.Seek(pos); } + void Seek64(uint64_t pos) { m_aligned.Seek64(pos); } /** outputs the keystream of size into */ void Keystream(unsigned char* c, size_t bytes); diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp index 6511f46adc..5d135f8987 100644 --- a/src/crypto/chacha_poly_aead.cpp +++ b/src/crypto/chacha_poly_aead.cpp @@ -62,7 +62,7 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int // block counter 0 for the poly1305 key // use lower 32bytes for the poly1305 key // (throws away 32 unused bytes (upper 32) from this ChaCha20 round) - m_chacha_main.Seek(0); + m_chacha_main.Seek64(0); m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key)); // if decrypting, verify the tag prior to decryption @@ -85,7 +85,7 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int if (m_cached_aad_seqnr != seqnr_aad) { m_cached_aad_seqnr = seqnr_aad; m_chacha_header.SetIV(seqnr_aad); - m_chacha_header.Seek(0); + m_chacha_header.Seek64(0); m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); } // crypt the AAD (3 bytes message length) with given position in AAD cipher instance keystream @@ -94,7 +94,7 @@ bool ChaCha20Poly1305AEAD::Crypt(uint64_t seqnr_payload, uint64_t seqnr_aad, int dest[2] = src[2] ^ m_aad_keystream_buffer[aad_pos + 2]; // Set the playload ChaCha instance block counter to 1 and crypt the payload - m_chacha_main.Seek(1); + m_chacha_main.Seek64(1); m_chacha_main.Crypt(src + CHACHA20_POLY1305_AEAD_AAD_LEN, dest + CHACHA20_POLY1305_AEAD_AAD_LEN, src_len - CHACHA20_POLY1305_AEAD_AAD_LEN); // If encrypting, calculate and append tag @@ -117,7 +117,7 @@ bool ChaCha20Poly1305AEAD::GetLength(uint32_t* len24_out, uint64_t seqnr_aad, in // we need to calculate the 64 keystream bytes since we reached a new aad sequence number m_cached_aad_seqnr = seqnr_aad; m_chacha_header.SetIV(seqnr_aad); // use LE for the nonce - m_chacha_header.Seek(0); // block counter 0 + m_chacha_header.Seek64(0); // block counter 0 m_chacha_header.Keystream(m_aad_keystream_buffer, CHACHA20_ROUND_OUTPUT); // write keystream to the cache } diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index d3eef7beb7..48a46258b0 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -136,7 +136,7 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk std::vector m = ParseHex(hex_message); ChaCha20 rng(key.data(), key.size()); rng.SetIV(nonce); - rng.Seek(seek); + rng.Seek64(seek); std::vector out = ParseHex(hexout); std::vector outres; outres.resize(out.size()); @@ -152,7 +152,7 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk if (!hex_message.empty()) { // Manually XOR with the keystream and compare the output rng.SetIV(nonce); - rng.Seek(seek); + rng.Seek64(seek); std::vector only_keystream(outres.size()); rng.Keystream(only_keystream.data(), only_keystream.size()); for (size_t i = 0; i != m.size(); i++) { @@ -631,7 +631,7 @@ static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aa // manually construct the AAD keystream cmp_ctx.SetIV(seqnr_aad); - cmp_ctx.Seek(0); + cmp_ctx.Seek64(0); cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64); BOOST_CHECK(memcmp(expected_aad_keystream.data(), cmp_ctx_buffer.data(), expected_aad_keystream.size()) == 0); // crypt the 3 length bytes and compare the length @@ -659,7 +659,7 @@ static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aa } // set nonce and block counter, output the keystream cmp_ctx.SetIV(seqnr_aad); - cmp_ctx.Seek(0); + cmp_ctx.Seek64(0); cmp_ctx.Keystream(cmp_ctx_buffer.data(), 64); // crypt the 3 length bytes and compare the length diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index 3f552a8cda..a109099394 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -30,7 +30,7 @@ FUZZ_TARGET(crypto_chacha20) chacha20.SetIV(fuzzed_data_provider.ConsumeIntegral()); }, [&] { - chacha20.Seek(fuzzed_data_provider.ConsumeIntegral()); + chacha20.Seek64(fuzzed_data_provider.ConsumeIntegral()); }, [&] { std::vector output(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); diff --git a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp index 1b89d55773..0b24b7c363 100644 --- a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp +++ b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp @@ -304,7 +304,7 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) }, [&] { uint64_t counter = fuzzed_data_provider.ConsumeIntegral(); - chacha20.Seek(counter); + chacha20.Seek64(counter); ctx.input[12] = counter; ctx.input[13] = counter >> 32; }, From 12ff72476ac0dbf8add736ad3fb5fad2eeab156c Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 21 Sep 2022 16:42:19 -0400 Subject: [PATCH 13/52] Make unrestricted ChaCha20 cipher not waste keystream bytes Co-authored-by: dhruv <856960+dhruv@users.noreply.github.com> --- src/crypto/chacha20.cpp | 29 ++++++++++++++++----- src/crypto/chacha20.h | 16 +++++++++--- src/test/crypto_tests.cpp | 18 +++++++++++++ src/test/fuzz/crypto_diff_fuzz_chacha20.cpp | 6 +++++ 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index c72ccccc65..80db0a3a83 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -297,6 +297,13 @@ inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, s void ChaCha20::Keystream(unsigned char* c, size_t bytes) { if (!bytes) return; + if (m_bufleft) { + unsigned reuse = std::min(m_bufleft, bytes); + memcpy(c, m_buffer + 64 - m_bufleft, reuse); + m_bufleft -= reuse; + bytes -= reuse; + c += reuse; + } if (bytes >= 64) { size_t blocks = bytes / 64; m_aligned.Keystream64(c, blocks); @@ -304,15 +311,25 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes) bytes -= blocks * 64; } if (bytes) { - unsigned char buffer[64]; - m_aligned.Keystream64(buffer, 1); - memcpy(c, buffer, bytes); + m_aligned.Keystream64(m_buffer, 1); + memcpy(c, m_buffer, bytes); + m_bufleft = 64 - bytes; } } void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) { if (!bytes) return; + if (m_bufleft) { + unsigned reuse = std::min(m_bufleft, bytes); + for (unsigned i = 0; i < reuse; i++) { + c[i] = m[i] ^ m_buffer[64 - m_bufleft + i]; + } + m_bufleft -= reuse; + bytes -= reuse; + c += reuse; + m += reuse; + } if (bytes >= 64) { size_t blocks = bytes / 64; m_aligned.Crypt64(m, c, blocks); @@ -321,10 +338,10 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes) bytes -= blocks * 64; } if (bytes) { - unsigned char buffer[64]; - m_aligned.Keystream64(buffer, 1); + m_aligned.Keystream64(m_buffer, 1); for (unsigned i = 0; i < bytes; i++) { - c[i] = m[i] ^ buffer[i]; + c[i] = m[i] ^ m_buffer[i]; } + m_bufleft = 64 - bytes; } } diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index fdef257ed3..715bf4e8e9 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -41,11 +41,13 @@ class ChaCha20Aligned void Crypt64(const unsigned char* input, unsigned char* output, size_t blocks); }; -/** Unrestricted ChaCha20 cipher. Seeks forward to a multiple of 64 bytes after every operation. */ +/** Unrestricted ChaCha20 cipher. */ class ChaCha20 { private: ChaCha20Aligned m_aligned; + unsigned char m_buffer[64] = {0}; + unsigned m_bufleft{0}; public: ChaCha20() = default; @@ -54,13 +56,21 @@ class ChaCha20 ChaCha20(const unsigned char* key, size_t keylen) : m_aligned(key, keylen) {} /** set key with flexible keylength (16 or 32 bytes; 32 recommended). */ - void SetKey(const unsigned char* key, size_t keylen) { m_aligned.SetKey(key, keylen); } + void SetKey(const unsigned char* key, size_t keylen) + { + m_aligned.SetKey(key, keylen); + m_bufleft = 0; + } /** set the 64-bit nonce. */ void SetIV(uint64_t iv) { m_aligned.SetIV(iv); } /** set the 64bit block counter (pos seeks to byte position 64*pos). */ - void Seek64(uint64_t pos) { m_aligned.Seek64(pos); } + void Seek64(uint64_t pos) + { + m_aligned.Seek64(pos); + m_bufleft = 0; + } /** outputs the keystream of size into */ void Keystream(unsigned char* c, size_t bytes); diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 48a46258b0..ae2aa46d50 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -500,6 +500,24 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector) "fab78c9"); } +BOOST_AUTO_TEST_CASE(chacha20_midblock) +{ + auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000"); + ChaCha20 c20{key.data(), 32}; + // get one block of keystream + unsigned char block[64]; + c20.Keystream(block, CHACHA20_ROUND_OUTPUT); + unsigned char b1[5], b2[7], b3[52]; + c20 = ChaCha20{key.data(), 32}; + c20.Keystream(b1, 5); + c20.Keystream(b2, 7); + c20.Keystream(b3, 52); + + BOOST_CHECK_EQUAL(0, memcmp(b1, block, 5)); + BOOST_CHECK_EQUAL(0, memcmp(b2, block + 5, 7)); + BOOST_CHECK_EQUAL(0, memcmp(b3, block + 12, 52)); +} + BOOST_AUTO_TEST_CASE(poly1305_testvector) { // RFC 7539, section 2.5.2. diff --git a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp index 0b24b7c363..1193a244db 100644 --- a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp +++ b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp @@ -310,20 +310,26 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) }, [&] { uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096); + // DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that. + uint64_t pos = ctx.input[12] + (((uint64_t)ctx.input[13]) << 32) + ((integralInRange + 63) >> 6); std::vector output(integralInRange); chacha20.Keystream(output.data(), output.size()); std::vector djb_output(integralInRange); ECRYPT_keystream_bytes(&ctx, djb_output.data(), djb_output.size()); assert(output == djb_output); + chacha20.Seek64(pos); }, [&] { uint32_t integralInRange = fuzzed_data_provider.ConsumeIntegralInRange(0, 4096); + // DJB's version seeks forward to a multiple of 64 bytes after every operation. Correct for that. + uint64_t pos = ctx.input[12] + (((uint64_t)ctx.input[13]) << 32) + ((integralInRange + 63) >> 6); std::vector output(integralInRange); const std::vector input = ConsumeFixedLengthByteVector(fuzzed_data_provider, output.size()); chacha20.Crypt(input.data(), output.data(), input.size()); std::vector djb_output(integralInRange); ECRYPT_encrypt_bytes(&ctx, input.data(), djb_output.data(), input.size()); assert(output == djb_output); + chacha20.Seek64(pos); }); } } From 5f05b27841af0bed1b6e7de5f46ffe33e5919e4d Mon Sep 17 00:00:00 2001 From: Martin Leitner-Ankerl Date: Fri, 10 Jun 2022 08:49:35 +0200 Subject: [PATCH 14/52] Add xoroshiro128++ PRNG Xoroshiro128++ is a fast non-cryptographic random generator. Reference implementation is available at https://prng.di.unimi.it/ Co-Authored-By: Pieter Wuille --- src/Makefile.test.include | 3 +- src/Makefile.test_util.include | 3 +- src/test/util/xoroshiro128plusplus.h | 71 +++++++++++++++++++++++++ src/test/xoroshiro128plusplus_tests.cpp | 29 ++++++++++ test/sanitizer_suppressions/ubsan | 2 + 5 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 src/test/util/xoroshiro128plusplus.h create mode 100644 src/test/xoroshiro128plusplus_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 4d867fdc2f..8537d2bcf4 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -162,7 +162,8 @@ BITCOIN_TESTS =\ test/validation_flush_tests.cpp \ test/validation_tests.cpp \ test/validationinterface_tests.cpp \ - test/versionbits_tests.cpp + test/versionbits_tests.cpp \ + test/xoroshiro128plusplus_tests.cpp if ENABLE_WALLET BITCOIN_TESTS += \ diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include index a4e8b3f842..6f5b76aae7 100644 --- a/src/Makefile.test_util.include +++ b/src/Makefile.test_util.include @@ -18,7 +18,8 @@ TEST_UTIL_H = \ test/util/str.h \ test/util/transaction_utils.h \ test/util/txmempool.h \ - test/util/validation.h + test/util/validation.h \ + test/util/xoroshiro128plusplus.h if ENABLE_WALLET TEST_UTIL_H += wallet/test/util.h diff --git a/src/test/util/xoroshiro128plusplus.h b/src/test/util/xoroshiro128plusplus.h new file mode 100644 index 0000000000..ac9f59b3f5 --- /dev/null +++ b/src/test/util/xoroshiro128plusplus.h @@ -0,0 +1,71 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H +#define BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H + +#include +#include + +/** xoroshiro128++ PRNG. Extremely fast, not appropriate for cryptographic purposes. + * + * Memory footprint is 128bit, period is 2^128 - 1. + * This class is not thread-safe. + * + * Reference implementation available at https://prng.di.unimi.it/xoroshiro128plusplus.c + * See https://prng.di.unimi.it/ + */ +class XoRoShiRo128PlusPlus +{ + uint64_t m_s0; + uint64_t m_s1; + + [[nodiscard]] constexpr static uint64_t rotl(uint64_t x, int n) + { + return (x << n) | (x >> (64 - n)); + } + + [[nodiscard]] constexpr static uint64_t SplitMix64(uint64_t& seedval) noexcept + { + uint64_t z = (seedval += UINT64_C(0x9e3779b97f4a7c15)); + z = (z ^ (z >> 30U)) * UINT64_C(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27U)) * UINT64_C(0x94d049bb133111eb); + return z ^ (z >> 31U); + } + +public: + using result_type = uint64_t; + + constexpr explicit XoRoShiRo128PlusPlus(uint64_t seedval) noexcept + : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) + { + } + + // no copy - that is dangerous, we don't want accidentally copy the RNG and then have two streams + // with exactly the same results. If you need a copy, call copy(). + XoRoShiRo128PlusPlus(const XoRoShiRo128PlusPlus&) = delete; + XoRoShiRo128PlusPlus& operator=(const XoRoShiRo128PlusPlus&) = delete; + + // allow moves + XoRoShiRo128PlusPlus(XoRoShiRo128PlusPlus&&) = default; + XoRoShiRo128PlusPlus& operator=(XoRoShiRo128PlusPlus&&) = default; + + ~XoRoShiRo128PlusPlus() = default; + + constexpr result_type operator()() noexcept + { + uint64_t s0 = m_s0, s1 = m_s1; + const uint64_t result = rotl(s0 + s1, 17) + s0; + s1 ^= s0; + m_s0 = rotl(s0, 49) ^ s1 ^ (s1 << 21); + m_s1 = rotl(s1, 28); + return result; + } + + static constexpr result_type min() noexcept { return std::numeric_limits::min(); } + static constexpr result_type max() noexcept { return std::numeric_limits::max(); } + static constexpr double entropy() noexcept { return 0.0; } +}; + +#endif // BITCOIN_TEST_UTIL_XOROSHIRO128PLUSPLUS_H diff --git a/src/test/xoroshiro128plusplus_tests.cpp b/src/test/xoroshiro128plusplus_tests.cpp new file mode 100644 index 0000000000..ea1b3e355f --- /dev/null +++ b/src/test/xoroshiro128plusplus_tests.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2022 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 + +#include + +BOOST_FIXTURE_TEST_SUITE(xoroshiro128plusplus_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(reference_values) +{ + // numbers generated from reference implementation + XoRoShiRo128PlusPlus rng(0); + BOOST_TEST(0x6f68e1e7e2646ee1 == rng()); + BOOST_TEST(0xbf971b7f454094ad == rng()); + BOOST_TEST(0x48f2de556f30de38 == rng()); + BOOST_TEST(0x6ea7c59f89bbfc75 == rng()); + + // seed with a random number + rng = XoRoShiRo128PlusPlus(0x1a26f3fa8546b47a); + BOOST_TEST(0xc8dc5e08d844ac7d == rng()); + BOOST_TEST(0x5b5f1f6d499dad1b == rng()); + BOOST_TEST(0xbeb0031f93313d6f == rng()); + BOOST_TEST(0xbfbcf4f43a264497 == rng()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index 67ef512895..2fa4e383e2 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -53,6 +53,7 @@ unsigned-integer-overflow:policy/fees.cpp unsigned-integer-overflow:prevector.h unsigned-integer-overflow:script/interpreter.cpp unsigned-integer-overflow:txmempool.cpp +unsigned-integer-overflow:xoroshiro128plusplus.h implicit-integer-sign-change:compat/stdin.cpp implicit-integer-sign-change:compressor.h implicit-integer-sign-change:crypto/ @@ -69,3 +70,4 @@ shift-base:crypto/ shift-base:hash.cpp shift-base:streams.h shift-base:util/bip32.cpp +shift-base:xoroshiro128plusplus.h From 38eaece67b1bc37b2f502348c5d7537480a34346 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 13 Jun 2022 18:17:28 -0400 Subject: [PATCH 15/52] Add fuzz test for testing that ChaCha20 works as a stream --- src/test/fuzz/crypto_chacha20.cpp | 108 ++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index a109099394..f1e239bcc8 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -43,3 +44,110 @@ FUZZ_TARGET(crypto_chacha20) }); } } + +namespace +{ + +/** Fuzzer that invokes ChaCha20::Crypt() or ChaCha20::Keystream multiple times: + once for a large block at once, and then the same data in chunks, comparing + the outcome. + + If UseCrypt, seeded Xoroshiro128++ output is used as input to Crypt(). + If not, Keystream() is used directly, or sequences of 0x00 are encrypted. +*/ +template +void ChaCha20SplitFuzz(FuzzedDataProvider& provider) +{ + // Determine key, iv, start position, length. + unsigned char key[32] = {0}; + auto key_bytes = provider.ConsumeBytes(32); + std::copy(key_bytes.begin(), key_bytes.end(), key); + uint64_t iv = provider.ConsumeIntegral(); + uint64_t total_bytes = provider.ConsumeIntegralInRange(0, 1000000); + /* ~x = 2^64 - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */ + uint64_t seek = provider.ConsumeIntegralInRange(0, ~(total_bytes >> 6)); + + // Initialize two ChaCha20 ciphers, with the same key/iv/position. + ChaCha20 crypt1(key, 32); + ChaCha20 crypt2(key, 32); + crypt1.SetIV(iv); + crypt1.Seek64(seek); + crypt2.SetIV(iv); + crypt2.Seek64(seek); + + // Construct vectors with data. + std::vector data1, data2; + data1.resize(total_bytes); + data2.resize(total_bytes); + + // If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based + // stream. + if constexpr (UseCrypt) { + uint64_t seed = provider.ConsumeIntegral(); + XoRoShiRo128PlusPlus rng(seed); + uint64_t bytes = 0; + while (bytes < (total_bytes & ~uint64_t{7})) { + uint64_t val = rng(); + WriteLE64(data1.data() + bytes, val); + WriteLE64(data2.data() + bytes, val); + bytes += 8; + } + if (bytes < total_bytes) { + unsigned char valbytes[8]; + uint64_t val = rng(); + WriteLE64(valbytes, val); + std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes); + std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes); + } + } + + // Whether UseCrypt is used or not, the two byte arrays must match. + assert(data1 == data2); + + // Encrypt data1, the whole array at once. + if constexpr (UseCrypt) { + crypt1.Crypt(data1.data(), data1.data(), total_bytes); + } else { + crypt1.Keystream(data1.data(), total_bytes); + } + + // Encrypt data2, in at most 256 chunks. + uint64_t bytes2 = 0; + int iter = 0; + while (true) { + bool is_last = (iter == 255) || (bytes2 == total_bytes) || provider.ConsumeBool(); + ++iter; + // Determine how many bytes to encrypt in this chunk: a fuzzer-determined + // amount for all but the last chunk (which processes all remaining bytes). + uint64_t now = is_last ? total_bytes - bytes2 : + provider.ConsumeIntegralInRange(0, total_bytes - bytes2); + // For each chunk, consider using Crypt() even when UseCrypt is false. + // This tests that Keystream() has the same behavior as Crypt() applied + // to 0x00 input bytes. + if (UseCrypt || provider.ConsumeBool()) { + crypt2.Crypt(data2.data() + bytes2, data2.data() + bytes2, now); + } else { + crypt2.Keystream(data2.data() + bytes2, now); + } + bytes2 += now; + if (is_last) break; + } + // We should have processed everything now. + assert(bytes2 == total_bytes); + // And the result should match. + assert(data1 == data2); +} + +} // namespace + +FUZZ_TARGET(chacha20_split_crypt) +{ + FuzzedDataProvider provider{buffer.data(), buffer.size()}; + ChaCha20SplitFuzz(provider); +} + +FUZZ_TARGET(chacha20_split_keystream) +{ + FuzzedDataProvider provider{buffer.data(), buffer.size()}; + ChaCha20SplitFuzz(provider); +} From 5d16f757639e2cc6e81db6e07bc1d5dd74abca6c Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 21 Sep 2022 17:31:54 -0400 Subject: [PATCH 16/52] Use ChaCha20 caching in FastRandomContext --- src/random.cpp | 14 ++++---------- src/random.h | 20 ++++---------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/src/random.cpp b/src/random.cpp index 23ea9ba6b7..32deca9f70 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -605,12 +605,9 @@ void FastRandomContext::RandomSeed() uint256 FastRandomContext::rand256() noexcept { - if (bytebuf_size < 32) { - FillByteBuffer(); - } + if (requires_seed) RandomSeed(); uint256 ret; - memcpy(ret.begin(), bytebuf + 64 - bytebuf_size, 32); - bytebuf_size -= 32; + rng.Keystream(ret.data(), ret.size()); return ret; } @@ -624,7 +621,7 @@ std::vector FastRandomContext::randbytes(size_t len) return ret; } -FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bitbuf_size(0) { rng.SetKey(seed.begin(), 32); } @@ -675,7 +672,7 @@ bool Random_SanityCheck() return true; } -FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bytebuf_size(0), bitbuf_size(0) +FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), bitbuf_size(0) { if (!fDeterministic) { return; @@ -688,12 +685,9 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce { requires_seed = from.requires_seed; rng = from.rng; - std::copy(std::begin(from.bytebuf), std::end(from.bytebuf), std::begin(bytebuf)); - bytebuf_size = from.bytebuf_size; bitbuf = from.bitbuf; bitbuf_size = from.bitbuf_size; from.requires_seed = true; - from.bytebuf_size = 0; from.bitbuf_size = 0; return *this; } diff --git a/src/random.h b/src/random.h index e890e909c7..63f28d3eb2 100644 --- a/src/random.h +++ b/src/random.h @@ -145,23 +145,11 @@ class FastRandomContext bool requires_seed; ChaCha20 rng; - unsigned char bytebuf[64]; - int bytebuf_size; - uint64_t bitbuf; int bitbuf_size; void RandomSeed(); - void FillByteBuffer() - { - if (requires_seed) { - RandomSeed(); - } - rng.Keystream(bytebuf, sizeof(bytebuf)); - bytebuf_size = sizeof(bytebuf); - } - void FillBitBuffer() { bitbuf = rand64(); @@ -185,10 +173,10 @@ class FastRandomContext /** Generate a random 64-bit integer. */ uint64_t rand64() noexcept { - if (bytebuf_size < 8) FillByteBuffer(); - uint64_t ret = ReadLE64(bytebuf + 64 - bytebuf_size); - bytebuf_size -= 8; - return ret; + if (requires_seed) RandomSeed(); + unsigned char buf[8]; + rng.Keystream(buf, 8); + return ReadLE64(buf); } /** Generate a random (bits)-bit integer. */ From f21994a02e1cc46d41995581b54222abc655be93 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 21 Sep 2022 17:32:32 -0400 Subject: [PATCH 17/52] Use ChaCha20Aligned in MuHash3072 code --- src/crypto/muhash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/muhash.cpp b/src/crypto/muhash.cpp index 26f0248663..d5ae67f374 100644 --- a/src/crypto/muhash.cpp +++ b/src/crypto/muhash.cpp @@ -299,7 +299,7 @@ Num3072 MuHash3072::ToNum3072(Span in) { unsigned char tmp[Num3072::BYTE_SIZE]; uint256 hashed_in{(HashWriter{} << in).GetSHA256()}; - ChaCha20(hashed_in.data(), hashed_in.size()).Keystream(tmp, Num3072::BYTE_SIZE); + ChaCha20Aligned(hashed_in.data(), hashed_in.size()).Keystream64(tmp, Num3072::BYTE_SIZE / 64); Num3072 out{tmp}; return out; From 62ec713961ade7b58e90c905395558a41e8a59f0 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 21 Sep 2022 17:39:48 -0400 Subject: [PATCH 18/52] Only support 32-byte keys in ChaCha20{,Aligned} --- src/bench/chacha20.cpp | 2 +- src/crypto/chacha20.cpp | 31 ++++++++------------- src/crypto/chacha20.h | 18 ++++++------ src/crypto/chacha_poly_aead.cpp | 5 ++-- src/crypto/muhash.cpp | 2 +- src/random.cpp | 6 ++-- src/test/crypto_tests.cpp | 11 ++++---- src/test/fuzz/crypto_chacha20.cpp | 12 ++++---- src/test/fuzz/crypto_diff_fuzz_chacha20.cpp | 12 ++++---- 9 files changed, 46 insertions(+), 53 deletions(-) diff --git a/src/bench/chacha20.cpp b/src/bench/chacha20.cpp index 8d1d1952bc..115cd064bd 100644 --- a/src/bench/chacha20.cpp +++ b/src/bench/chacha20.cpp @@ -14,7 +14,7 @@ static const uint64_t BUFFER_SIZE_LARGE = 1024*1024; static void CHACHA20(benchmark::Bench& bench, size_t buffersize) { std::vector key(32,0); - ChaCha20 ctx(key.data(), key.size()); + ChaCha20 ctx(key.data()); ctx.SetIV(0); ctx.Seek64(0); std::vector in(buffersize,0); diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index 80db0a3a83..c5eee5ccfd 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -22,30 +22,21 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | ( #define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0) static const unsigned char sigma[] = "expand 32-byte k"; -static const unsigned char tau[] = "expand 16-byte k"; -void ChaCha20Aligned::SetKey(const unsigned char* k, size_t keylen) +void ChaCha20Aligned::SetKey32(const unsigned char* k) { - const unsigned char *constants; - + input[0] = ReadLE32(sigma + 0); + input[1] = ReadLE32(sigma + 4); + input[2] = ReadLE32(sigma + 8); + input[3] = ReadLE32(sigma + 12); input[4] = ReadLE32(k + 0); input[5] = ReadLE32(k + 4); input[6] = ReadLE32(k + 8); input[7] = ReadLE32(k + 12); - if (keylen == 32) { /* recommended */ - k += 16; - constants = sigma; - } else { /* keylen == 16 */ - constants = tau; - } - input[8] = ReadLE32(k + 0); - input[9] = ReadLE32(k + 4); - input[10] = ReadLE32(k + 8); - input[11] = ReadLE32(k + 12); - input[0] = ReadLE32(constants + 0); - input[1] = ReadLE32(constants + 4); - input[2] = ReadLE32(constants + 8); - input[3] = ReadLE32(constants + 12); + input[8] = ReadLE32(k + 16); + input[9] = ReadLE32(k + 20); + input[10] = ReadLE32(k + 24); + input[11] = ReadLE32(k + 28); input[12] = 0; input[13] = 0; input[14] = 0; @@ -57,9 +48,9 @@ ChaCha20Aligned::ChaCha20Aligned() memset(input, 0, sizeof(input)); } -ChaCha20Aligned::ChaCha20Aligned(const unsigned char* k, size_t keylen) +ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32) { - SetKey(k, keylen); + SetKey32(key32); } void ChaCha20Aligned::SetIV(uint64_t iv) diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 715bf4e8e9..1119bf6323 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -20,11 +20,11 @@ class ChaCha20Aligned public: ChaCha20Aligned(); - /** Initialize a cipher with specified key (see SetKey for arguments). */ - ChaCha20Aligned(const unsigned char* key, size_t keylen); + /** Initialize a cipher with specified 32-byte key. */ + ChaCha20Aligned(const unsigned char* key32); - /** set key with flexible keylength (16 or 32 bytes; 32 recommended). */ - void SetKey(const unsigned char* key, size_t keylen); + /** set 32-byte key. */ + void SetKey32(const unsigned char* key32); /** set the 64-bit nonce. */ void SetIV(uint64_t iv); @@ -52,13 +52,13 @@ class ChaCha20 public: ChaCha20() = default; - /** Initialize a cipher with specified key (see SetKey for arguments). */ - ChaCha20(const unsigned char* key, size_t keylen) : m_aligned(key, keylen) {} + /** Initialize a cipher with specified 32-byte key. */ + ChaCha20(const unsigned char* key32) : m_aligned(key32) {} - /** set key with flexible keylength (16 or 32 bytes; 32 recommended). */ - void SetKey(const unsigned char* key, size_t keylen) + /** set 32-byte key. */ + void SetKey32(const unsigned char* key32) { - m_aligned.SetKey(key, keylen); + m_aligned.SetKey32(key32); m_bufleft = 0; } diff --git a/src/crypto/chacha_poly_aead.cpp b/src/crypto/chacha_poly_aead.cpp index 5d135f8987..119ad6902f 100644 --- a/src/crypto/chacha_poly_aead.cpp +++ b/src/crypto/chacha_poly_aead.cpp @@ -36,8 +36,9 @@ ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_ assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN); assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN); - m_chacha_header.SetKey(K_1, CHACHA20_POLY1305_AEAD_KEY_LEN); - m_chacha_main.SetKey(K_2, CHACHA20_POLY1305_AEAD_KEY_LEN); + static_assert(CHACHA20_POLY1305_AEAD_KEY_LEN == 32); + m_chacha_header.SetKey32(K_1); + m_chacha_main.SetKey32(K_2); // set the cached sequence number to uint64 max which hints for an unset cache. // we can't hit uint64 max since the rekey rule (which resets the sequence number) is 1GB diff --git a/src/crypto/muhash.cpp b/src/crypto/muhash.cpp index d5ae67f374..471ee6af97 100644 --- a/src/crypto/muhash.cpp +++ b/src/crypto/muhash.cpp @@ -299,7 +299,7 @@ Num3072 MuHash3072::ToNum3072(Span in) { unsigned char tmp[Num3072::BYTE_SIZE]; uint256 hashed_in{(HashWriter{} << in).GetSHA256()}; - ChaCha20Aligned(hashed_in.data(), hashed_in.size()).Keystream64(tmp, Num3072::BYTE_SIZE / 64); + ChaCha20Aligned(hashed_in.data()).Keystream64(tmp, Num3072::BYTE_SIZE / 64); Num3072 out{tmp}; return out; diff --git a/src/random.cpp b/src/random.cpp index 32deca9f70..5f50c001cd 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -599,7 +599,7 @@ uint256 GetRandHash() noexcept void FastRandomContext::RandomSeed() { uint256 seed = GetRandHash(); - rng.SetKey(seed.begin(), 32); + rng.SetKey32(seed.begin()); requires_seed = false; } @@ -623,7 +623,7 @@ std::vector FastRandomContext::randbytes(size_t len) FastRandomContext::FastRandomContext(const uint256& seed) noexcept : requires_seed(false), bitbuf_size(0) { - rng.SetKey(seed.begin(), 32); + rng.SetKey32(seed.begin()); } bool Random_SanityCheck() @@ -678,7 +678,7 @@ FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_se return; } uint256 seed; - rng.SetKey(seed.begin(), 32); + rng.SetKey32(seed.begin()); } FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexcept diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index ae2aa46d50..ddeaed761e 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -133,8 +133,9 @@ static void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, b static void TestChaCha20(const std::string &hex_message, const std::string &hexkey, uint64_t nonce, uint64_t seek, const std::string& hexout) { std::vector key = ParseHex(hexkey); + assert(key.size() == 32); std::vector m = ParseHex(hex_message); - ChaCha20 rng(key.data(), key.size()); + ChaCha20 rng(key.data()); rng.SetIV(nonce); rng.Seek64(seek); std::vector out = ParseHex(hexout); @@ -460,7 +461,7 @@ BOOST_AUTO_TEST_CASE(aes_cbc_testvectors) { BOOST_AUTO_TEST_CASE(chacha20_testvector) { - // Test vector from RFC 7539 + // Test vectors from RFC 7539 // test encryption TestChaCha20("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756" @@ -503,12 +504,12 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector) BOOST_AUTO_TEST_CASE(chacha20_midblock) { auto key = ParseHex("0000000000000000000000000000000000000000000000000000000000000000"); - ChaCha20 c20{key.data(), 32}; + ChaCha20 c20{key.data()}; // get one block of keystream unsigned char block[64]; c20.Keystream(block, CHACHA20_ROUND_OUTPUT); unsigned char b1[5], b2[7], b3[52]; - c20 = ChaCha20{key.data(), 32}; + c20 = ChaCha20{key.data()}; c20.Keystream(b1, 5); c20.Keystream(b2, 7); c20.Keystream(b3, 52); @@ -635,7 +636,7 @@ static void TestChaCha20Poly1305AEAD(bool must_succeed, unsigned int expected_aa ChaCha20Poly1305AEAD aead(aead_K_1.data(), aead_K_1.size(), aead_K_2.data(), aead_K_2.size()); // create a chacha20 instance to compare against - ChaCha20 cmp_ctx(aead_K_1.data(), 32); + ChaCha20 cmp_ctx(aead_K_1.data()); // encipher bool res = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, ciphertext_buf.data(), ciphertext_buf.size(), plaintext_buf.data(), plaintext_buf.size(), true); diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index f1e239bcc8..3fa445096a 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -17,15 +17,15 @@ FUZZ_TARGET(crypto_chacha20) ChaCha20 chacha20; if (fuzzed_data_provider.ConsumeBool()) { - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange(16, 32)); - chacha20 = ChaCha20{key.data(), key.size()}; + const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); + chacha20 = ChaCha20{key.data()}; } LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { CallOneOf( fuzzed_data_provider, [&] { - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange(16, 32)); - chacha20.SetKey(key.data(), key.size()); + std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); + chacha20.SetKey32(key.data()); }, [&] { chacha20.SetIV(fuzzed_data_provider.ConsumeIntegral()); @@ -68,8 +68,8 @@ void ChaCha20SplitFuzz(FuzzedDataProvider& provider) uint64_t seek = provider.ConsumeIntegralInRange(0, ~(total_bytes >> 6)); // Initialize two ChaCha20 ciphers, with the same key/iv/position. - ChaCha20 crypt1(key, 32); - ChaCha20 crypt2(key, 32); + ChaCha20 crypt1(key); + ChaCha20 crypt2(key); crypt1.SetIV(iv); crypt1.Seek64(seek); crypt2.SetIV(iv); diff --git a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp index 1193a244db..9d650fc492 100644 --- a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp +++ b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp @@ -277,10 +277,10 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) } if (fuzzed_data_provider.ConsumeBool()) { - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange(16, 32)); - chacha20 = ChaCha20{key.data(), key.size()}; + const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); + chacha20 = ChaCha20{key.data()}; ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0); - // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey() does + // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0}; ECRYPT_ivsetup(&ctx, iv); } @@ -289,10 +289,10 @@ FUZZ_TARGET(crypto_diff_fuzz_chacha20) CallOneOf( fuzzed_data_provider, [&] { - const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, fuzzed_data_provider.ConsumeIntegralInRange(16, 32)); - chacha20.SetKey(key.data(), key.size()); + const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); + chacha20.SetKey32(key.data()); ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0); - // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey() does + // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0}; ECRYPT_ivsetup(&ctx, iv); }, From 93aee8bbdad808b7009279b67470d496cc26b936 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 21 Sep 2022 18:01:57 -0400 Subject: [PATCH 19/52] Inline ChaCha20 32-byte specific constants --- src/crypto/chacha20.cpp | 138 +++++++++----------- src/crypto/chacha20.h | 2 +- src/test/fuzz/crypto_diff_fuzz_chacha20.cpp | 15 ++- 3 files changed, 71 insertions(+), 84 deletions(-) diff --git a/src/crypto/chacha20.cpp b/src/crypto/chacha20.cpp index c5eee5ccfd..6934cef163 100644 --- a/src/crypto/chacha20.cpp +++ b/src/crypto/chacha20.cpp @@ -21,26 +21,20 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | ( #define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0) -static const unsigned char sigma[] = "expand 32-byte k"; - void ChaCha20Aligned::SetKey32(const unsigned char* k) { - input[0] = ReadLE32(sigma + 0); - input[1] = ReadLE32(sigma + 4); - input[2] = ReadLE32(sigma + 8); - input[3] = ReadLE32(sigma + 12); - input[4] = ReadLE32(k + 0); - input[5] = ReadLE32(k + 4); - input[6] = ReadLE32(k + 8); - input[7] = ReadLE32(k + 12); - input[8] = ReadLE32(k + 16); - input[9] = ReadLE32(k + 20); - input[10] = ReadLE32(k + 24); - input[11] = ReadLE32(k + 28); - input[12] = 0; - input[13] = 0; - input[14] = 0; - input[15] = 0; + input[0] = ReadLE32(k + 0); + input[1] = ReadLE32(k + 4); + input[2] = ReadLE32(k + 8); + input[3] = ReadLE32(k + 12); + input[4] = ReadLE32(k + 16); + input[5] = ReadLE32(k + 20); + input[6] = ReadLE32(k + 24); + input[7] = ReadLE32(k + 28); + input[8] = 0; + input[9] = 0; + input[10] = 0; + input[11] = 0; } ChaCha20Aligned::ChaCha20Aligned() @@ -55,45 +49,41 @@ ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32) void ChaCha20Aligned::SetIV(uint64_t iv) { - input[14] = iv; - input[15] = iv >> 32; + input[10] = iv; + input[11] = iv >> 32; } void ChaCha20Aligned::Seek64(uint64_t pos) { - input[12] = pos; - input[13] = pos >> 32; + input[8] = pos; + input[9] = pos >> 32; } inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks) { uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; - uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; if (!blocks) return; - j0 = input[0]; - j1 = input[1]; - j2 = input[2]; - j3 = input[3]; - j4 = input[4]; - j5 = input[5]; - j6 = input[6]; - j7 = input[7]; - j8 = input[8]; - j9 = input[9]; - j10 = input[10]; - j11 = input[11]; - j12 = input[12]; - j13 = input[13]; - j14 = input[14]; - j15 = input[15]; + j4 = input[0]; + j5 = input[1]; + j6 = input[2]; + j7 = input[3]; + j8 = input[4]; + j9 = input[5]; + j10 = input[6]; + j11 = input[7]; + j12 = input[8]; + j13 = input[9]; + j14 = input[10]; + j15 = input[11]; for (;;) { - x0 = j0; - x1 = j1; - x2 = j2; - x3 = j3; + x0 = 0x61707865; + x1 = 0x3320646e; + x2 = 0x79622d32; + x3 = 0x6b206574; x4 = j4; x5 = j5; x6 = j6; @@ -119,10 +109,10 @@ inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks) QUARTERROUND( x3, x4, x9,x14); ); - x0 += j0; - x1 += j1; - x2 += j2; - x3 += j3; + x0 += 0x61707865; + x1 += 0x3320646e; + x2 += 0x79622d32; + x3 += 0x6b206574; x4 += j4; x5 += j5; x6 += j6; @@ -157,8 +147,8 @@ inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks) WriteLE32(c + 60, x15); if (blocks == 1) { - input[12] = j12; - input[13] = j13; + input[8] = j12; + input[9] = j13; return; } blocks -= 1; @@ -169,32 +159,28 @@ inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks) inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, size_t blocks) { uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; - uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; if (!blocks) return; - j0 = input[0]; - j1 = input[1]; - j2 = input[2]; - j3 = input[3]; - j4 = input[4]; - j5 = input[5]; - j6 = input[6]; - j7 = input[7]; - j8 = input[8]; - j9 = input[9]; - j10 = input[10]; - j11 = input[11]; - j12 = input[12]; - j13 = input[13]; - j14 = input[14]; - j15 = input[15]; + j4 = input[0]; + j5 = input[1]; + j6 = input[2]; + j7 = input[3]; + j8 = input[4]; + j9 = input[5]; + j10 = input[6]; + j11 = input[7]; + j12 = input[8]; + j13 = input[9]; + j14 = input[10]; + j15 = input[11]; for (;;) { - x0 = j0; - x1 = j1; - x2 = j2; - x3 = j3; + x0 = 0x61707865; + x1 = 0x3320646e; + x2 = 0x79622d32; + x3 = 0x6b206574; x4 = j4; x5 = j5; x6 = j6; @@ -220,10 +206,10 @@ inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, s QUARTERROUND( x3, x4, x9,x14); ); - x0 += j0; - x1 += j1; - x2 += j2; - x3 += j3; + x0 += 0x61707865; + x1 += 0x3320646e; + x2 += 0x79622d32; + x3 += 0x6b206574; x4 += j4; x5 += j5; x6 += j6; @@ -275,8 +261,8 @@ inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, s WriteLE32(c + 60, x15); if (blocks == 1) { - input[12] = j12; - input[13] = j13; + input[8] = j12; + input[9] = j13; return; } blocks -= 1; diff --git a/src/crypto/chacha20.h b/src/crypto/chacha20.h index 1119bf6323..b286ef59fe 100644 --- a/src/crypto/chacha20.h +++ b/src/crypto/chacha20.h @@ -15,7 +15,7 @@ class ChaCha20Aligned { private: - uint32_t input[16]; + uint32_t input[12]; public: ChaCha20Aligned(); diff --git a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp index 9d650fc492..78fee48de6 100644 --- a/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp +++ b/src/test/fuzz/crypto_diff_fuzz_chacha20.cpp @@ -267,24 +267,25 @@ void ECRYPT_keystream_bytes(ECRYPT_ctx* x, u8* stream, u32 bytes) FUZZ_TARGET(crypto_diff_fuzz_chacha20) { + static const unsigned char ZEROKEY[32] = {0}; FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; ChaCha20 chacha20; ECRYPT_ctx ctx; - // D. J. Bernstein doesn't initialise ctx to 0 while Bitcoin Core initialises chacha20 to 0 in the constructor - for (int i = 0; i < 16; i++) { - ctx.input[i] = 0; - } if (fuzzed_data_provider.ConsumeBool()) { const std::vector key = ConsumeFixedLengthByteVector(fuzzed_data_provider, 32); chacha20 = ChaCha20{key.data()}; ECRYPT_keysetup(&ctx, key.data(), key.size() * 8, 0); - // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does - uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - ECRYPT_ivsetup(&ctx, iv); + } else { + // The default ChaCha20 constructor is equivalent to using the all-0 key. + ECRYPT_keysetup(&ctx, ZEROKEY, 256, 0); } + // ECRYPT_keysetup() doesn't set the counter and nonce to 0 while SetKey32() does + static const uint8_t iv[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + ECRYPT_ivsetup(&ctx, iv); + LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 3000) { CallOneOf( fuzzed_data_provider, From fb243d25f754da8f01793b41e2d225b917f3e5d7 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 21 Sep 2022 19:12:21 -0400 Subject: [PATCH 20/52] Improve test vectors for ChaCha20 --- src/test/crypto_tests.cpp | 113 +++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 27 deletions(-) diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index ddeaed761e..0ecca3ce2e 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -138,10 +138,9 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk ChaCha20 rng(key.data()); rng.SetIV(nonce); rng.Seek64(seek); - std::vector out = ParseHex(hexout); std::vector outres; - outres.resize(out.size()); - assert(hex_message.empty() || m.size() == out.size()); + outres.resize(hexout.size() / 2); + assert(hex_message.empty() || m.size() * 2 == hexout.size()); // perform the ChaCha20 round(s), if message is provided it will output the encrypted ciphertext otherwise the keystream if (!hex_message.empty()) { @@ -149,7 +148,7 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk } else { rng.Keystream(outres.data(), outres.size()); } - BOOST_CHECK(out == outres); + BOOST_CHECK_EQUAL(hexout, HexStr(outres)); if (!hex_message.empty()) { // Manually XOR with the keystream and compare the output rng.SetIV(nonce); @@ -159,7 +158,7 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk for (size_t i = 0; i != m.size(); i++) { outres[i] = m[i] ^ only_keystream[i]; } - BOOST_CHECK(out == outres); + BOOST_CHECK_EQUAL(hexout, HexStr(outres)); } } @@ -461,7 +460,88 @@ BOOST_AUTO_TEST_CASE(aes_cbc_testvectors) { BOOST_AUTO_TEST_CASE(chacha20_testvector) { - // Test vectors from RFC 7539 + // RFC 7539/8439 A.1 Test Vector #1: + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000000", + 0, 0, + "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" + "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); + + // RFC 7539/8439 A.1 Test Vector #2: + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000000", + 0, 1, + "9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed" + "29b721769ce64e43d57133b074d839d531ed1f28510afb45ace10a1f4b794d6f"); + + // RFC 7539/8439 A.1 Test Vector #3: + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000001", + 0, 1, + "3aeb5224ecf849929b9d828db1ced4dd832025e8018b8160b82284f3c949aa5a" + "8eca00bbb4a73bdad192b5c42f73f2fd4e273644c8b36125a64addeb006c13a0"); + + // RFC 7539/8439 A.1 Test Vector #4: + TestChaCha20("", + "00ff000000000000000000000000000000000000000000000000000000000000", + 0, 2, + "72d54dfbf12ec44b362692df94137f328fea8da73990265ec1bbbea1ae9af0ca" + "13b25aa26cb4a648cb9b9d1be65b2c0924a66c54d545ec1b7374f4872e99f096"); + + // RFC 7539/8439 A.1 Test Vector #5: + TestChaCha20("", + "0000000000000000000000000000000000000000000000000000000000000000", + 0x200000000000000, 0, + "c2c64d378cd536374ae204b9ef933fcd1a8b2288b3dfa49672ab765b54ee27c7" + "8a970e0e955c14f3a88e741b97c286f75f8fc299e8148362fa198a39531bed6d"); + + // RFC 7539/8439 A.2 Test Vector #1: + TestChaCha20("0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + 0, 0, + "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7" + "da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); + + // RFC 7539/8439 A.2 Test Vector #2: + TestChaCha20("416e79207375626d697373696f6e20746f20746865204945544620696e74656e" + "6465642062792074686520436f6e7472696275746f7220666f72207075626c69" + "636174696f6e20617320616c6c206f722070617274206f6620616e2049455446" + "20496e7465726e65742d4472616674206f722052464320616e6420616e792073" + "746174656d656e74206d6164652077697468696e2074686520636f6e74657874" + "206f6620616e204945544620616374697669747920697320636f6e7369646572" + "656420616e20224945544620436f6e747269627574696f6e222e205375636820" + "73746174656d656e747320696e636c756465206f72616c2073746174656d656e" + "747320696e20494554462073657373696f6e732c2061732077656c6c20617320" + "7772697474656e20616e6420656c656374726f6e696320636f6d6d756e696361" + "74696f6e73206d61646520617420616e792074696d65206f7220706c6163652c" + "207768696368206172652061646472657373656420746f", + "0000000000000000000000000000000000000000000000000000000000000001", + 0x200000000000000, 1, + "a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec" + "2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d" + "4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e527950" + "42bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85a" + "d00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259d" + "c4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b" + "0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6c" + "cc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0b" + "c39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f" + "5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e6" + "98ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab" + "7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221"); + + // RFC 7539/8439 A.2 Test Vector #3: + TestChaCha20("2754776173206272696c6c69672c20616e642074686520736c6974687920746f" + "7665730a446964206779726520616e642067696d626c6520696e207468652077" + "6162653a0a416c6c206d696d737920776572652074686520626f726f676f7665" + "732c0a416e6420746865206d6f6d65207261746873206f757467726162652e", + "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0", + 0x200000000000000, 42, + "62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf" + "166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553eb" + "f39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f77" + "04c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1"); // test encryption TestChaCha20("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756" @@ -478,27 +558,6 @@ BOOST_AUTO_TEST_CASE(chacha20_testvector) "224f51f3401bd9e12fde276fb8631ded8c131f823d2c06e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b9334794cb" "a40c63e34cdea212c4cf07d41b769a6749f3f630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53ac40c5945398b6eda1a" "832c89c167eacd901d7e2bf363"); - - // Test vectors from https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 - TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000000", 0, 0, - "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b" - "8f41518a11cc387b669b2ee6586"); - TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000001", 0, 0, - "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d79" - "2b1c43fea817e9ad275ae546963"); - TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000000", 0x0100000000000000ULL, 0, - "de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b52770" - "62eb7a0433e445f41e3"); - TestChaCha20("", "0000000000000000000000000000000000000000000000000000000000000000", 1, 0, - "ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc4" - "97a0b466e7d6bbdb0041b2f586b"); - TestChaCha20("", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", 0x0706050403020100ULL, 0, - "f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3b" - "e59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc1" - "18be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5" - "a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5" - "360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78" - "fab78c9"); } BOOST_AUTO_TEST_CASE(chacha20_midblock) From 511aa4f1c7508f15cab8d7e58007900ad6fd3d5d Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 18 Jan 2023 16:27:06 -0500 Subject: [PATCH 21/52] Add unit test for ChaCha20's new caching --- src/test/crypto_tests.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 0ecca3ce2e..ed851b5266 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -160,6 +160,27 @@ static void TestChaCha20(const std::string &hex_message, const std::string &hexk } BOOST_CHECK_EQUAL(hexout, HexStr(outres)); } + + // Repeat 10x, but fragmented into 3 chunks, to exercise the ChaCha20 class's caching. + for (int i = 0; i < 10; ++i) { + size_t lens[3]; + lens[0] = InsecureRandRange(hexout.size() / 2U + 1U); + lens[1] = InsecureRandRange(hexout.size() / 2U + 1U - lens[0]); + lens[2] = hexout.size() / 2U - lens[0] - lens[1]; + + rng.Seek64(seek); + outres.assign(hexout.size() / 2U, 0); + size_t pos = 0; + for (int j = 0; j < 3; ++j) { + if (!hex_message.empty()) { + rng.Crypt(m.data() + pos, outres.data() + pos, lens[j]); + } else { + rng.Keystream(outres.data() + pos, lens[j]); + } + pos += lens[j]; + } + BOOST_CHECK_EQUAL(hexout, HexStr(outres)); + } } static void TestPoly1305(const std::string &hexmessage, const std::string &hexkey, const std::string& hextag) From b3b673f7048cce1d1368819abb0b58b7c6699fa5 Mon Sep 17 00:00:00 2001 From: fanquake Date: Thu, 29 Jul 2021 15:57:45 +0800 Subject: [PATCH 22/52] mapport: require miniupnpc API version 17 or later Version 17 is currently the latest version, and has been available since the release of 2.1. See: https://github.com/miniupnp/miniupnp/blob/master/miniupnpc/apiversions.txt. --- configure.ac | 9 +++++---- doc/dependencies.md | 2 +- src/mapport.cpp | 10 +++------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index aa1a45c527..b1cd189277 100644 --- a/configure.ac +++ b/configure.ac @@ -1416,14 +1416,15 @@ if test "$use_upnp" != "no"; then [AC_CHECK_LIB([miniupnpc], [upnpDiscover], [MINIUPNPC_LIBS="$MINIUPNPC_LIBS -lminiupnpc"], [have_miniupnpc=no], [$MINIUPNPC_LIBS])], [have_miniupnpc=no] ) - dnl The minimum supported miniUPnPc API version is set to 10. This keeps compatibility - dnl with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. + + dnl The minimum supported miniUPnPc API version is set to 17. This excludes + dnl versions with known vulnerabilities. if test "$have_miniupnpc" != "no"; then AC_MSG_CHECKING([whether miniUPnPc API version is supported]) AC_PREPROC_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ - #if MINIUPNPC_API_VERSION >= 10 + #if MINIUPNPC_API_VERSION >= 17 // Everything is okay #else # error miniUPnPc API version is too old @@ -1432,7 +1433,7 @@ if test "$use_upnp" != "no"; then AC_MSG_RESULT([yes]) ],[ AC_MSG_RESULT([no]) - AC_MSG_WARN([miniUPnPc API version < 10 is unsupported, disabling UPnP support.]) + AC_MSG_WARN([miniUPnPc API version < 17 is unsupported, disabling UPnP support.]) have_miniupnpc=no ]) fi diff --git a/doc/dependencies.md b/doc/dependencies.md index 3349c81c46..ec205e4b51 100644 --- a/doc/dependencies.md +++ b/doc/dependencies.md @@ -36,7 +36,7 @@ You can find installation instructions in the `build-*.md` file for your platfor | Dependency | Releases | Version used | Minimum required | Runtime | | --- | --- | --- | --- | --- | | [libnatpmp](../depends/packages/libnatpmp.mk) | [link](https://github.com/miniupnp/libnatpmp/) | commit [07004b9...](https://github.com/bitcoin/bitcoin/pull/25917) | | No | -| [MiniUPnPc](../depends/packages/miniupnpc.mk) | [link](https://miniupnp.tuxfamily.org/) | [2.2.2](https://github.com/bitcoin/bitcoin/pull/20421) | 1.9 | No | +| [MiniUPnPc](../depends/packages/miniupnpc.mk) | [link](https://miniupnp.tuxfamily.org/) | [2.2.2](https://github.com/bitcoin/bitcoin/pull/20421) | 2.1 | No | ### Notifications | Dependency | Releases | Version used | Minimum required | Runtime | diff --git a/src/mapport.cpp b/src/mapport.cpp index e6a473c185..84b889f22d 100644 --- a/src/mapport.cpp +++ b/src/mapport.cpp @@ -27,9 +27,9 @@ #include #include #include -// The minimum supported miniUPnPc API version is set to 10. This keeps compatibility -// with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages. -static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"); +// The minimum supported miniUPnPc API version is set to 17. This excludes +// versions with known vulnerabilities. +static_assert(MINIUPNPC_API_VERSION >= 17, "miniUPnPc API version >= 17 assumed"); #endif // USE_UPNP #include @@ -159,11 +159,7 @@ static bool ProcessUpnp() char lanaddr[64]; int error = 0; -#if MINIUPNPC_API_VERSION < 14 - devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error); -#else devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error); -#endif struct UPNPUrls urls; struct IGDdatas data; From 3c9cea1340fd1358d6854209d782922864945eb0 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 31 Jan 2023 18:04:31 -0500 Subject: [PATCH 23/52] Add simulation-based CCoinsViewCache fuzzer The fuzzer goes through a sequence of operations that get applied to both a real stack of CCoinsViewCache objects, and to simulation data, comparing the two at the end. --- src/Makefile.test.include | 1 + src/test/fuzz/coinscache_sim.cpp | 461 +++++++++++++++++++++++++++++++ 2 files changed, 462 insertions(+) create mode 100644 src/test/fuzz/coinscache_sim.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 4d867fdc2f..d6992640ff 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -248,6 +248,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/chain.cpp \ test/fuzz/checkqueue.cpp \ test/fuzz/coins_view.cpp \ + test/fuzz/coinscache_sim.cpp \ test/fuzz/connman.cpp \ test/fuzz/crypto.cpp \ test/fuzz/crypto_aes256.cpp \ diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp new file mode 100644 index 0000000000..c3f732f075 --- /dev/null +++ b/src/test/fuzz/coinscache_sim.cpp @@ -0,0 +1,461 @@ +// Copyright (c) 2023 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +/** Number of distinct COutPoint values used in this test. */ +constexpr uint32_t NUM_OUTPOINTS = 256; +/** Number of distinct Coin values used in this test (ignoring nHeight). */ +constexpr uint32_t NUM_COINS = 256; +/** Maximum number CCoinsViewCache objects used in this test. */ +constexpr uint32_t MAX_CACHES = 4; +/** Data type large enough to hold NUM_COINS-1. */ +using coinidx_type = uint8_t; + +struct PrecomputedData +{ + //! Randomly generated COutPoint values. + COutPoint outpoints[NUM_OUTPOINTS]; + + //! Randomly generated Coin values. + Coin coins[NUM_COINS]; + + PrecomputedData() + { + static const uint8_t PREFIX_O[1] = {'o'}; /** Hash prefix for outpoint hashes. */ + static const uint8_t PREFIX_S[1] = {'s'}; /** Hash prefix for coins scriptPubKeys. */ + static const uint8_t PREFIX_M[1] = {'m'}; /** Hash prefix for coins nValue/fCoinBase. */ + + for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) { + uint32_t idx = (i * 1200U) >> 12; /* Map 3 or 4 entries to same txid. */ + const uint8_t ser[4] = {uint8_t(idx), uint8_t(idx >> 8), uint8_t(idx >> 16), uint8_t(idx >> 24)}; + CSHA256().Write(PREFIX_O, 1).Write(ser, sizeof(ser)).Finalize(outpoints[i].hash.begin()); + outpoints[i].n = i; + } + + for (uint32_t i = 0; i < NUM_COINS; ++i) { + const uint8_t ser[4] = {uint8_t(i), uint8_t(i >> 8), uint8_t(i >> 16), uint8_t(i >> 24)}; + uint256 hash; + CSHA256().Write(PREFIX_S, 1).Write(ser, sizeof(ser)).Finalize(hash.begin()); + /* Convert hash to scriptPubkeys. */ + switch (i % 5U) { + case 0: /* P2PKH */ + coins[i].out.scriptPubKey.resize(25); + coins[i].out.scriptPubKey[0] = OP_DUP; + coins[i].out.scriptPubKey[1] = OP_HASH160; + coins[i].out.scriptPubKey[2] = 20; + std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 3); + coins[i].out.scriptPubKey[23] = OP_EQUALVERIFY; + coins[i].out.scriptPubKey[24] = OP_CHECKSIG; + break; + case 1: /* P2SH */ + coins[i].out.scriptPubKey.resize(23); + coins[i].out.scriptPubKey[0] = OP_HASH160; + coins[i].out.scriptPubKey[1] = 20; + std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2); + coins[i].out.scriptPubKey[12] = OP_EQUAL; + break; + case 2: /* P2WPKH */ + coins[i].out.scriptPubKey.resize(22); + coins[i].out.scriptPubKey[0] = OP_0; + coins[i].out.scriptPubKey[1] = 20; + std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2); + break; + case 3: /* P2WSH */ + coins[i].out.scriptPubKey.resize(34); + coins[i].out.scriptPubKey[0] = OP_0; + coins[i].out.scriptPubKey[1] = 32; + std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2); + break; + case 4: /* P2TR */ + coins[i].out.scriptPubKey.resize(34); + coins[i].out.scriptPubKey[0] = OP_1; + coins[i].out.scriptPubKey[1] = 32; + std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2); + break; + } + /* Hash again to construct nValue and fCoinBase. */ + CSHA256().Write(PREFIX_M, 1).Write(ser, sizeof(ser)).Finalize(hash.begin()); + coins[i].out.nValue = CAmount(hash.GetUint64(0) % MAX_MONEY); + coins[i].fCoinBase = (hash.GetUint64(1) & 7) == 0; + coins[i].nHeight = 0; /* Real nHeight used in simulation is set dynamically. */ + } + } +}; + +enum class EntryType : uint8_t +{ + /* This entry in the cache does not exist (so we'd have to look in the parent cache). */ + NONE, + + /* This entry in the cache corresponds to an unspent coin. */ + UNSPENT, + + /* This entry in the cache corresponds to a spent coin. */ + SPENT, +}; + +struct CacheEntry +{ + /* Type of entry. */ + EntryType entrytype; + + /* Index in the coins array this entry corresponds to (only if entrytype == UNSPENT). */ + coinidx_type coinidx; + + /* nHeight value for this entry (so the coins[coinidx].nHeight value is ignored; only if entrytype == UNSPENT). */ + uint32_t height; +}; + +struct CacheLevel +{ + CacheEntry entry[NUM_OUTPOINTS]; + + void Wipe() { + for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) { + entry[i].entrytype = EntryType::NONE; + } + } +}; + +/** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB). */ +class CoinsViewBottom final : public CCoinsView +{ + std::map m_data; + +public: + bool GetCoin(const COutPoint& outpoint, Coin& coin) const final + { + auto it = m_data.find(outpoint); + if (it == m_data.end()) { + return false; + } else { + coin = it->second; + return true; + } + } + + bool HaveCoin(const COutPoint& outpoint) const final + { + return m_data.count(outpoint); + } + + uint256 GetBestBlock() const final { return {}; } + std::vector GetHeadBlocks() const final { return {}; } + std::unique_ptr Cursor() const final { return {}; } + size_t EstimateSize() const final { return m_data.size(); } + + bool BatchWrite(CCoinsMap& data, const uint256&, bool erase) final + { + for (auto it = data.begin(); it != data.end(); it = erase ? data.erase(it) : std::next(it)) { + if (it->second.flags & CCoinsCacheEntry::DIRTY) { + if (it->second.coin.IsSpent()) { + m_data.erase(it->first); + } else if (erase) { + m_data[it->first] = std::move(it->second.coin); + } else { + m_data[it->first] = it->second.coin; + } + } else { + /* For non-dirty entries being written, compare them with what we have. */ + if (it->second.coin.IsSpent()) { + assert(m_data.count(it->first) == 0); + } else { + auto it2 = m_data.find(it->first); + assert(it2 != m_data.end()); + assert(it->second.coin.out == it2->second.out); + assert(it->second.coin.fCoinBase == it2->second.fCoinBase); + assert(it->second.coin.nHeight == it2->second.nHeight); + } + } + } + return true; + } +}; + +} // namespace + +FUZZ_TARGET(coinscache_sim) +{ + /** Precomputed COutPoint and CCoins values. */ + static const PrecomputedData data; + + /** Dummy coinsview instance (base of the hierarchy). */ + CoinsViewBottom bottom; + /** Real CCoinsViewCache objects. */ + std::vector> caches; + /** Simulated cache data (sim_caches[0] matches bottom, sim_caches[i+1] matches caches[i]). */ + CacheLevel sim_caches[MAX_CACHES + 1]; + /** Current height in the simulation. */ + uint32_t current_height = 1U; + + // Initialize bottom simulated cache. + sim_caches[0].Wipe(); + + /** Helper lookup function in the simulated cache stack. */ + auto lookup = [&](uint32_t outpointidx, int sim_idx = -1) -> std::optional> { + uint32_t cache_idx = sim_idx == -1 ? caches.size() : sim_idx; + while (true) { + const auto& entry = sim_caches[cache_idx].entry[outpointidx]; + if (entry.entrytype == EntryType::UNSPENT) { + return {{entry.coinidx, entry.height}}; + } else if (entry.entrytype == EntryType::SPENT) { + return std::nullopt; + }; + if (cache_idx == 0) break; + --cache_idx; + } + return std::nullopt; + }; + + /** Flush changes in top cache to the one below. */ + auto flush = [&]() { + assert(caches.size() >= 1); + auto& cache = sim_caches[caches.size()]; + auto& prev_cache = sim_caches[caches.size() - 1]; + for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) { + if (cache.entry[outpointidx].entrytype != EntryType::NONE) { + prev_cache.entry[outpointidx] = cache.entry[outpointidx]; + cache.entry[outpointidx].entrytype = EntryType::NONE; + } + } + }; + + // Main simulation loop: read commands from the fuzzer input, and apply them + // to both the real cache stack and the simulation. + FuzzedDataProvider provider(buffer.data(), buffer.size()); + LIMITED_WHILE(provider.remaining_bytes(), 10000) { + // Every operation (except "Change height") moves current height forward, + // so it functions as a kind of epoch, making ~all UTXOs unique. + ++current_height; + // Make sure there is always at least one CCoinsViewCache. + if (caches.empty()) { + caches.emplace_back(new CCoinsViewCache(&bottom)); + sim_caches[caches.size()].Wipe(); + } + + // Execute command. + CallOneOf( + provider, + + [&]() { // GetCoin + uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1); + // Look up in simulation data. + auto sim = lookup(outpointidx); + // Look up in real caches. + Coin realcoin; + auto real = caches.back()->GetCoin(data.outpoints[outpointidx], realcoin); + // Compare results. + if (!sim.has_value()) { + assert(!real); + } else { + assert(!realcoin.IsSpent()); + const auto& simcoin = data.coins[sim->first]; + assert(realcoin.out == simcoin.out); + assert(realcoin.fCoinBase == simcoin.fCoinBase); + assert(realcoin.nHeight == sim->second); + } + }, + + [&]() { // HaveCoin + uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1); + // Look up in simulation data. + auto sim = lookup(outpointidx); + // Look up in real caches. + auto real = caches.back()->HaveCoin(data.outpoints[outpointidx]); + // Compare results. + assert(sim.has_value() == real); + }, + + [&]() { // HaveCoinInCache + uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1); + // Invoke on real cache (there is no equivalent in simulation, so nothing to compare result with). + (void)caches.back()->HaveCoinInCache(data.outpoints[outpointidx]); + }, + + [&]() { // AccessCoin + uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1); + // Look up in simulation data. + auto sim = lookup(outpointidx); + // Look up in real caches. + const auto& realcoin = caches.back()->AccessCoin(data.outpoints[outpointidx]); + // Compare results. + if (!sim.has_value()) { + assert(realcoin.IsSpent()); + } else { + assert(!realcoin.IsSpent()); + const auto& simcoin = data.coins[sim->first]; + assert(simcoin.out == realcoin.out); + assert(simcoin.fCoinBase == realcoin.fCoinBase); + assert(realcoin.nHeight == sim->second); + } + }, + + [&]() { // AddCoin (only possible_overwrite if necessary) + uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1); + uint32_t coinidx = provider.ConsumeIntegralInRange(0, NUM_COINS - 1); + // Look up in simulation data (to know whether we must set possible_overwrite or not). + auto sim = lookup(outpointidx); + // Invoke on real caches. + Coin coin = data.coins[coinidx]; + coin.nHeight = current_height; + caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), sim.has_value()); + // Apply to simulation data. + auto& entry = sim_caches[caches.size()].entry[outpointidx]; + entry.entrytype = EntryType::UNSPENT; + entry.coinidx = coinidx; + entry.height = current_height; + }, + + [&]() { // AddCoin (always possible_overwrite) + uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1); + uint32_t coinidx = provider.ConsumeIntegralInRange(0, NUM_COINS - 1); + // Invoke on real caches. + Coin coin = data.coins[coinidx]; + coin.nHeight = current_height; + caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), true); + // Apply to simulation data. + auto& entry = sim_caches[caches.size()].entry[outpointidx]; + entry.entrytype = EntryType::UNSPENT; + entry.coinidx = coinidx; + entry.height = current_height; + }, + + [&]() { // SpendCoin (moveto = nullptr) + uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1); + // Invoke on real caches. + caches.back()->SpendCoin(data.outpoints[outpointidx], nullptr); + // Apply to simulation data. + sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT; + }, + + [&]() { // SpendCoin (with moveto) + uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1); + // Look up in simulation data (to compare the returned *moveto with). + auto sim = lookup(outpointidx); + // Invoke on real caches. + Coin realcoin; + caches.back()->SpendCoin(data.outpoints[outpointidx], &realcoin); + // Apply to simulation data. + sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT; + // Compare *moveto with the value expected based on simulation data. + if (!sim.has_value()) { + assert(realcoin.IsSpent()); + } else { + assert(!realcoin.IsSpent()); + const auto& simcoin = data.coins[sim->first]; + assert(simcoin.out == realcoin.out); + assert(simcoin.fCoinBase == realcoin.fCoinBase); + assert(realcoin.nHeight == sim->second); + } + }, + + [&]() { // Uncache + uint32_t outpointidx = provider.ConsumeIntegralInRange(0, NUM_OUTPOINTS - 1); + // Apply to real caches (there is no equivalent in our simulation). + caches.back()->Uncache(data.outpoints[outpointidx]); + }, + + [&]() { // Add a cache level (if not already at the max). + if (caches.size() != MAX_CACHES) { + // Apply to real caches. + caches.emplace_back(new CCoinsViewCache(&*caches.back())); + // Apply to simulation data. + sim_caches[caches.size()].Wipe(); + } + }, + + [&]() { // Remove a cache level. + // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data). + caches.pop_back(); + }, + + [&]() { // Flush. + // Apply to simulation data. + flush(); + // Apply to real caches. + caches.back()->Flush(); + }, + + [&]() { // Sync. + // Apply to simulation data (note that in our simulation, syncing and flushing is the same thing). + flush(); + // Apply to real caches. + caches.back()->Sync(); + }, + + [&]() { // Flush + ReallocateCache. + // Apply to simulation data. + flush(); + // Apply to real caches. + caches.back()->Flush(); + caches.back()->ReallocateCache(); + }, + + [&]() { // GetCacheSize + (void)caches.back()->GetCacheSize(); + }, + + [&]() { // DynamicMemoryUsage + (void)caches.back()->DynamicMemoryUsage(); + }, + + [&]() { // Change height + current_height = provider.ConsumeIntegralInRange(1, current_height - 1); + } + ); + } + + // Full comparison between caches and simulation data, from bottom to top, + // as AccessCoin on a higher cache may affect caches below it. + for (unsigned sim_idx = 1; sim_idx <= caches.size(); ++sim_idx) { + auto& cache = *caches[sim_idx - 1]; + size_t cache_size = 0; + + for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) { + cache_size += cache.HaveCoinInCache(data.outpoints[outpointidx]); + const auto& real = cache.AccessCoin(data.outpoints[outpointidx]); + auto sim = lookup(outpointidx, sim_idx); + if (!sim.has_value()) { + assert(real.IsSpent()); + } else { + assert(!real.IsSpent()); + assert(real.out == data.coins[sim->first].out); + assert(real.fCoinBase == data.coins[sim->first].fCoinBase); + assert(real.nHeight == sim->second); + } + } + + // HaveCoinInCache ignores spent coins, so GetCacheSize() may exceed it. */ + assert(cache.GetCacheSize() >= cache_size); + } + + // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0]. + for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) { + Coin realcoin; + bool real = bottom.GetCoin(data.outpoints[outpointidx], realcoin); + auto sim = lookup(outpointidx, 0); + if (!sim.has_value()) { + assert(!real); + } else { + assert(!realcoin.IsSpent()); + assert(realcoin.out == data.coins[sim->first].out); + assert(realcoin.fCoinBase == data.coins[sim->first].fCoinBase); + assert(realcoin.nHeight == sim->second); + } + } +} From b0ff31084006ac7d4a7afba3190ca75f5f8441af Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 1 Feb 2023 18:28:08 -0500 Subject: [PATCH 24/52] Add CCoinsViewCache::SanityCheck() and use it in fuzz test --- src/coins.cpp | 17 +++++++++++++++++ src/coins.h | 3 +++ src/test/fuzz/coinscache_sim.cpp | 9 ++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/coins.cpp b/src/coins.cpp index e98bf816ab..8d99019bb0 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -314,6 +314,23 @@ void CCoinsViewCache::ReallocateCache() ::new (&cacheCoins) CCoinsMap(); } +void CCoinsViewCache::SanityCheck() const +{ + size_t recomputed_usage = 0; + for (const auto& [_, entry] : cacheCoins) { + unsigned attr = 0; + if (entry.flags & CCoinsCacheEntry::DIRTY) attr |= 1; + if (entry.flags & CCoinsCacheEntry::FRESH) attr |= 2; + if (entry.coin.IsSpent()) attr |= 4; + // Only 5 combinations are possible. + assert(attr != 2 && attr != 4 && attr != 7); + + // Recompute cachedCoinsUsage. + recomputed_usage += entry.coin.DynamicMemoryUsage(); + } + assert(recomputed_usage == cachedCoinsUsage); +} + static const size_t MIN_TRANSACTION_OUTPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut(), PROTOCOL_VERSION); static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_OUTPUT_WEIGHT; diff --git a/src/coins.h b/src/coins.h index 710b8c7c83..a2764d32bb 100644 --- a/src/coins.h +++ b/src/coins.h @@ -320,6 +320,9 @@ class CCoinsViewCache : public CCoinsViewBacked //! See: https://stackoverflow.com/questions/42114044/how-to-release-unordered-map-memory void ReallocateCache(); + //! Run an internal sanity check on the cache data structure. */ + void SanityCheck() const; + private: /** * @note this is marked const, but may actually append to `cacheCoins`, increasing diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index c3f732f075..b794888ca2 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -51,7 +51,8 @@ struct PrecomputedData const uint8_t ser[4] = {uint8_t(i), uint8_t(i >> 8), uint8_t(i >> 16), uint8_t(i >> 24)}; uint256 hash; CSHA256().Write(PREFIX_S, 1).Write(ser, sizeof(ser)).Finalize(hash.begin()); - /* Convert hash to scriptPubkeys. */ + /* Convert hash to scriptPubkeys (of different lengths, so SanityCheck's cached memory + * usage check has a chance to detect mismatches). */ switch (i % 5U) { case 0: /* P2PKH */ coins[i].out.scriptPubKey.resize(25); @@ -381,6 +382,7 @@ FUZZ_TARGET(coinscache_sim) [&]() { // Remove a cache level. // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data). + caches.back()->SanityCheck(); caches.pop_back(); }, @@ -420,6 +422,11 @@ FUZZ_TARGET(coinscache_sim) ); } + // Sanity check all the remaining caches + for (const auto& cache : caches) { + cache->SanityCheck(); + } + // Full comparison between caches and simulation data, from bottom to top, // as AccessCoin on a higher cache may affect caches below it. for (unsigned sim_idx = 1; sim_idx <= caches.size(); ++sim_idx) { From 59e6828bb5b56a2354a80059d3f660f551f3e207 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 1 Feb 2023 18:52:11 -0500 Subject: [PATCH 25/52] Add deterministic mode to CCoinsViewCache --- src/coins.cpp | 7 +++++-- src/coins.h | 5 ++++- src/test/fuzz/coins_view.cpp | 2 +- src/test/fuzz/coinscache_sim.cpp | 4 ++-- src/util/hasher.cpp | 5 ++++- src/util/hasher.h | 2 +- 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index 8d99019bb0..5a6ae525a7 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -32,7 +32,10 @@ bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, std::unique_ptr CCoinsViewBacked::Cursor() const { return base->Cursor(); } size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } -CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn) : CCoinsViewBacked(baseIn) {} +CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn, bool deterministic) : + CCoinsViewBacked(baseIn), m_deterministic(deterministic), + cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic)) +{} size_t CCoinsViewCache::DynamicMemoryUsage() const { return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage; @@ -311,7 +314,7 @@ void CCoinsViewCache::ReallocateCache() // Cache should be empty when we're calling this. assert(cacheCoins.size() == 0); cacheCoins.~CCoinsMap(); - ::new (&cacheCoins) CCoinsMap(); + ::new (&cacheCoins) CCoinsMap(0, SaltedOutpointHasher(/*deterministic=*/m_deterministic)); } void CCoinsViewCache::SanityCheck() const diff --git a/src/coins.h b/src/coins.h index a2764d32bb..dd336b210a 100644 --- a/src/coins.h +++ b/src/coins.h @@ -211,6 +211,9 @@ class CCoinsViewBacked : public CCoinsView /** CCoinsView that adds a memory cache for transactions to another CCoinsView */ class CCoinsViewCache : public CCoinsViewBacked { +private: + const bool m_deterministic; + protected: /** * Make mutable so that we can "fill the cache" even from Get-methods @@ -223,7 +226,7 @@ class CCoinsViewCache : public CCoinsViewBacked mutable size_t cachedCoinsUsage{0}; public: - CCoinsViewCache(CCoinsView *baseIn); + CCoinsViewCache(CCoinsView *baseIn, bool deterministic = false); /** * By deleting the copy constructor, we prevent accidentally using it when one intends to create a cache on top of a base cache. diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp index e75dc3ce91..e80c772aa4 100644 --- a/src/test/fuzz/coins_view.cpp +++ b/src/test/fuzz/coins_view.cpp @@ -46,7 +46,7 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view) { FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; CCoinsView backend_coins_view; - CCoinsViewCache coins_view_cache{&backend_coins_view}; + CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true}; COutPoint random_out_point; Coin random_coin; CMutableTransaction random_mutable_transaction; diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index b794888ca2..02575f2731 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -245,7 +245,7 @@ FUZZ_TARGET(coinscache_sim) ++current_height; // Make sure there is always at least one CCoinsViewCache. if (caches.empty()) { - caches.emplace_back(new CCoinsViewCache(&bottom)); + caches.emplace_back(new CCoinsViewCache(&bottom, /*deterministic=*/true)); sim_caches[caches.size()].Wipe(); } @@ -374,7 +374,7 @@ FUZZ_TARGET(coinscache_sim) [&]() { // Add a cache level (if not already at the max). if (caches.size() != MAX_CACHES) { // Apply to real caches. - caches.emplace_back(new CCoinsViewCache(&*caches.back())); + caches.emplace_back(new CCoinsViewCache(&*caches.back(), /*deterministic=*/true)); // Apply to simulation data. sim_caches[caches.size()].Wipe(); } diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp index a3a3f7a429..81e9b990e1 100644 --- a/src/util/hasher.cpp +++ b/src/util/hasher.cpp @@ -9,7 +9,10 @@ SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand()), k1(GetRand()) {} -SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand()), k1(GetRand()) {} +SaltedOutpointHasher::SaltedOutpointHasher(bool deterministic) : + k0(deterministic ? 0x8e819f2607a18de6 : GetRand()), + k1(deterministic ? 0xf4020d2e3983b0eb : GetRand()) +{} SaltedSipHasher::SaltedSipHasher() : m_k0(GetRand()), m_k1(GetRand()) {} diff --git a/src/util/hasher.h b/src/util/hasher.h index 82d278b086..506ae9415d 100644 --- a/src/util/hasher.h +++ b/src/util/hasher.h @@ -36,7 +36,7 @@ class SaltedOutpointHasher const uint64_t k0, k1; public: - SaltedOutpointHasher(); + SaltedOutpointHasher(bool deterministic = false); /** * Having the hash noexcept allows libstdc++'s unordered_map to recalculate From 77192c959816dc8daee138d88bd6f3250ce3bdb6 Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Sun, 27 Nov 2022 08:36:53 -0500 Subject: [PATCH 26/52] cli: include local ("unreachable") peers in -netinfo table --- src/bitcoin-cli.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index e6e33007d5..df8fb7cece 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -55,7 +55,10 @@ static constexpr int DEFAULT_WAIT_CLIENT_TIMEOUT = 0; static const bool DEFAULT_NAMED=false; static const int CONTINUE_EXECUTION=-1; static constexpr int8_t UNKNOWN_NETWORK{-1}; -static constexpr std::array NETWORKS{"ipv4", "ipv6", "onion", "i2p", "cjdns"}; +// See GetNetworkName() in netbase.cpp +static constexpr std::array NETWORKS{"not_publicly_routable", "ipv4", "ipv6", "onion", "i2p", "cjdns", "internal"}; +static constexpr std::array NETWORK_SHORT_NAMES{"npr", "ipv4", "ipv6", "onion", "i2p", "cjdns", "int"}; +static constexpr std::array UNREACHABLE_NETWORK_IDS{/*not_publicly_routable*/0, /*internal*/6}; /** Default number of blocks to generate for RPC generatetoaddress. */ static const std::string DEFAULT_NBLOCKS = "1"; @@ -289,7 +292,7 @@ class AddrinfoRequestHandler : public BaseRequestHandler // Prepare result to return to user. UniValue result{UniValue::VOBJ}, addresses{UniValue::VOBJ}; uint64_t total{0}; // Total address count - for (size_t i = 0; i < NETWORKS.size(); ++i) { + for (size_t i = 1; i < NETWORKS.size() - 1; ++i) { addresses.pushKV(NETWORKS[i], counts.at(i)); total += counts.at(i); } @@ -506,7 +509,7 @@ class NetinfoRequestHandler : public BaseRequestHandler const bool is_addr_relay_enabled{peer["addr_relay_enabled"].isNull() ? false : peer["addr_relay_enabled"].get_bool()}; const bool is_bip152_hb_from{peer["bip152_hb_from"].get_bool()}; const bool is_bip152_hb_to{peer["bip152_hb_to"].get_bool()}; - m_peers.push_back({addr, sub_version, conn_type, network, age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay}); + m_peers.push_back({addr, sub_version, conn_type, NETWORK_SHORT_NAMES[network_id], age, min_ping, ping, addr_processed, addr_rate_limited, last_blck, last_recv, last_send, last_trxn, peer_id, mapped_as, version, is_addr_relay_enabled, is_bip152_hb_from, is_bip152_hb_to, is_outbound, is_tx_relay}); m_max_addr_length = std::max(addr.length() + 1, m_max_addr_length); m_max_addr_processed_length = std::max(ToString(addr_processed).length(), m_max_addr_processed_length); m_max_addr_rate_limited_length = std::max(ToString(addr_rate_limited).length(), m_max_addr_rate_limited_length); @@ -571,6 +574,13 @@ class NetinfoRequestHandler : public BaseRequestHandler reachable_networks.push_back(network_id); } }; + + for (const size_t network_id : UNREACHABLE_NETWORK_IDS) { + if (m_counts.at(2).at(network_id) == 0) continue; + result += strprintf("%8s", NETWORK_SHORT_NAMES.at(network_id)); // column header + reachable_networks.push_back(network_id); + } + result += " total block"; if (m_manual_peers_count) result += " manual"; @@ -636,7 +646,7 @@ class NetinfoRequestHandler : public BaseRequestHandler " \"manual\" - peer we manually added using RPC addnode or the -addnode/-connect config options\n" " \"feeler\" - short-lived connection for testing addresses\n" " \"addr\" - address fetch; short-lived connection for requesting addresses\n" - " net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", or \"cjdns\")\n" + " net Network the peer connected through (\"ipv4\", \"ipv6\", \"onion\", \"i2p\", \"cjdns\", or \"npr\" (not publicly routable))\n" " mping Minimum observed ping time, in milliseconds (ms)\n" " ping Last observed ping time, in milliseconds (ms)\n" " send Time since last message sent to the peer, in seconds\n" From 561848aaf2d67791e92754f3d11813bc53959a8f Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 3 Feb 2023 10:33:31 -0500 Subject: [PATCH 27/52] Exercise non-DIRTY spent coins in caches in fuzz test --- src/test/fuzz/coinscache_sim.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index 02575f2731..f350c9d032 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -133,7 +133,13 @@ struct CacheLevel } }; -/** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB). */ +/** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB). + * + * The initial state consists of the empty UTXO set, though coins whose output index + * is 3 (mod 5) always have GetCoin() succeed (but returning an IsSpent() coin unless a UTXO + * exists). Coins whose output index is 4 (mod 5) have GetCoin() always succeed after being spent. + * This exercises code paths with spent, non-DIRTY cache entries. + */ class CoinsViewBottom final : public CCoinsView { std::map m_data; @@ -143,6 +149,10 @@ class CoinsViewBottom final : public CCoinsView { auto it = m_data.find(outpoint); if (it == m_data.end()) { + if ((outpoint.n % 5) == 3) { + coin.Clear(); + return true; + } return false; } else { coin = it->second; @@ -164,7 +174,7 @@ class CoinsViewBottom final : public CCoinsView { for (auto it = data.begin(); it != data.end(); it = erase ? data.erase(it) : std::next(it)) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { - if (it->second.coin.IsSpent()) { + if (it->second.coin.IsSpent() && (it->first.n % 5) != 4) { m_data.erase(it->first); } else if (erase) { m_data[it->first] = std::move(it->second.coin); @@ -173,10 +183,10 @@ class CoinsViewBottom final : public CCoinsView } } else { /* For non-dirty entries being written, compare them with what we have. */ + auto it2 = m_data.find(it->first); if (it->second.coin.IsSpent()) { - assert(m_data.count(it->first) == 0); + assert(it2 == m_data.end() || it2->second.IsSpent()); } else { - auto it2 = m_data.find(it->first); assert(it2 != m_data.end()); assert(it->second.coin.out == it2->second.out); assert(it->second.coin.fCoinBase == it2->second.fCoinBase); @@ -262,9 +272,9 @@ FUZZ_TARGET(coinscache_sim) auto real = caches.back()->GetCoin(data.outpoints[outpointidx], realcoin); // Compare results. if (!sim.has_value()) { - assert(!real); + assert(!real || realcoin.IsSpent()); } else { - assert(!realcoin.IsSpent()); + assert(real && !realcoin.IsSpent()); const auto& simcoin = data.coins[sim->first]; assert(realcoin.out == simcoin.out); assert(realcoin.fCoinBase == simcoin.fCoinBase); @@ -457,9 +467,9 @@ FUZZ_TARGET(coinscache_sim) bool real = bottom.GetCoin(data.outpoints[outpointidx], realcoin); auto sim = lookup(outpointidx, 0); if (!sim.has_value()) { - assert(!real); + assert(!real || realcoin.IsSpent()); } else { - assert(!realcoin.IsSpent()); + assert(real && !realcoin.IsSpent()); assert(realcoin.out == data.coins[sim->first].out); assert(realcoin.fCoinBase == data.coins[sim->first].fCoinBase); assert(realcoin.nHeight == sim->second); From dee8549be39f841a24c3c8a3af3c5f787b9ad880 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Fri, 3 Feb 2023 17:23:26 +0100 Subject: [PATCH 28/52] test: simplify and speedup mempool_updatefromblock.py by using MiniWallet --- test/functional/mempool_updatefromblock.py | 57 ++++++---------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py index 68cbb5dbed..8350e9c91e 100755 --- a/test/functional/mempool_updatefromblock.py +++ b/test/functional/mempool_updatefromblock.py @@ -7,14 +7,12 @@ Test mempool update of transaction descendants/ancestors information (count, size) when transactions have been re-added from a disconnected block to the mempool. """ +from math import ceil import time -from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal -from test_framework.address import key_to_p2pkh -from test_framework.wallet_util import bytes_to_wif -from test_framework.key import ECKey +from test_framework.wallet import MiniWallet class MempoolUpdateFromBlockTest(BitcoinTestFramework): @@ -22,15 +20,7 @@ def set_test_params(self): self.num_nodes = 1 self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100']] - def get_new_address(self): - key = ECKey() - key.generate() - pubkey = key.get_pubkey().get_bytes() - address = key_to_p2pkh(pubkey) - self.priv_keys.append(bytes_to_wif(key.get_bytes())) - return address - - def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', end_address='', fee=Decimal(0.00100000)): + def transaction_graph_test(self, size, n_tx_to_mine=None, fee=100_000): """Create an acyclic tournament (a type of directed graph) of transactions and use it for testing. Keyword arguments: @@ -45,14 +35,7 @@ def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', e More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory) """ - - self.priv_keys = [self.nodes[0].get_deterministic_priv_key().key] - if not start_input_txid: - start_input_txid = self.nodes[0].getblock(self.nodes[0].getblockhash(1))['tx'][0] - - if not end_address: - end_address = self.get_new_address() - + wallet = MiniWallet(self.nodes[0]) first_block_hash = '' tx_id = [] tx_size = [] @@ -61,41 +44,31 @@ def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', e self.log.debug('Preparing transaction #{}...'.format(i)) # Prepare inputs. if i == 0: - inputs = [{'txid': start_input_txid, 'vout': 0}] - inputs_value = self.nodes[0].gettxout(start_input_txid, 0)['value'] + inputs = [wallet.get_utxo()] # let MiniWallet provide a start UTXO else: inputs = [] - inputs_value = 0 for j, tx in enumerate(tx_id[0:i]): # Transaction tx[K] is a child of each of previous transactions tx[0]..tx[K-1] at their output K-1. vout = i - j - 1 - inputs.append({'txid': tx_id[j], 'vout': vout}) - inputs_value += self.nodes[0].gettxout(tx, vout)['value'] - - self.log.debug('inputs={}'.format(inputs)) - self.log.debug('inputs_value={}'.format(inputs_value)) + inputs.append(wallet.get_utxo(txid=tx_id[j], vout=vout)) # Prepare outputs. tx_count = i + 1 if tx_count < size: # Transaction tx[K] is an ancestor of each of subsequent transactions tx[K+1]..tx[N-1]. n_outputs = size - tx_count - output_value = ((inputs_value - fee) / Decimal(n_outputs)).quantize(Decimal('0.00000001')) - outputs = {} - for _ in range(n_outputs): - outputs[self.get_new_address()] = output_value else: - output_value = (inputs_value - fee).quantize(Decimal('0.00000001')) - outputs = {end_address: output_value} - - self.log.debug('output_value={}'.format(output_value)) - self.log.debug('outputs={}'.format(outputs)) + n_outputs = 1 # Create a new transaction. - unsigned_raw_tx = self.nodes[0].createrawtransaction(inputs, outputs) - signed_raw_tx = self.nodes[0].signrawtransactionwithkey(unsigned_raw_tx, self.priv_keys) - tx_id.append(self.nodes[0].sendrawtransaction(signed_raw_tx['hex'])) - tx_size.append(self.nodes[0].getmempoolentry(tx_id[-1])['vsize']) + new_tx = wallet.send_self_transfer_multi( + from_node=self.nodes[0], + utxos_to_spend=inputs, + num_outputs=n_outputs, + fee_per_output=ceil(fee / n_outputs) + ) + tx_id.append(new_tx['txid']) + tx_size.append(new_tx['tx'].get_vsize()) if tx_count in n_tx_to_mine: # The created transactions are mined into blocks by batches. From aafa5e945cef7a4f65ddadcf548932dd4e27ada1 Mon Sep 17 00:00:00 2001 From: laanwj <126646+laanwj@users.noreply.github.com> Date: Tue, 7 Feb 2023 02:37:22 +0100 Subject: [PATCH 29/52] Remove laanwj from trusted-keys allow-revsig-commits list generated using: git log --format="%H %ce" --merges 577bd51a4b8de066466a445192c1c653872657e2..master | grep laanwj | cut -c -40 >> allow-revsig-commits Tree-SHA512: e665d1f3f6ae45ad435cb2802d49988f5133d695b145aa2dc65af95c052e562e0afaf585c351a41529985b4229965cf555f7197a44c90ba7daaea7a28975648d --- contrib/verify-commits/allow-revsig-commits | 175 ++++++++++++++++++++ contrib/verify-commits/trusted-keys | 1 - 2 files changed, 175 insertions(+), 1 deletion(-) diff --git a/contrib/verify-commits/allow-revsig-commits b/contrib/verify-commits/allow-revsig-commits index 0bb299b8fa..0c43d9cce5 100644 --- a/contrib/verify-commits/allow-revsig-commits +++ b/contrib/verify-commits/allow-revsig-commits @@ -643,3 +643,178 @@ b7365f0545b1a6862e3277b2b2139ee0d5aee1cf 4bd0e9b90a39c5c6a016b83882ae44cb4d28f1f8 7438ceac716fdfe6621728c05e718eaa89dd89aa 4e3efd47e0d50c6cd1dc81ccc9669a5b2658f495 +5ab6a942764bf6577ae311f2551153dde3d4830c +b04f42efe31e23e15cc945efe0de906ed2eadb2b +ceae0eb7e31f9d3495a13a23df7372e5e870b572 +5bf65ec66e5986c9188e3f6234f1c5c0f8dc7f90 +55c9e2d790fa2e137ccd0d91e6cf3e2d0bff4813 +ba29911e21c88f49780c6c87f94ff8ed6e764a9d +fffff0abb9c71f0af83a7925db3c293b3bb12158 +aaeb315ff0f7956449a92736160795f0140369e3 +0dd34773334c7f4db7b05df30ee61b011795b46d +2598720d6c1ef15288045599de7e65f8707d09bc +bc83710fdcc09d8e427e77457df107acc9db1be5 +ddd7a39aa960ee3639ef1e59b2e53852e0862c52 +0808c88d7bd992d5c9ded0009c9563f6177b4035 +a085a554913ae8f4ed83afac830ce6dc39c9cc65 +b1a824dd06aa58618947783edee2dd891b5204dc +a4e066af8573dcefb11dff120e1c09e8cf7f40c2 +58b9d6cf9e9b801be9c677a3ae121e5d2950ce66 +7377ed778c6d832ecd291e65b2789af7bac2ae2b +c3a41ad980cc5149de3f9ec8414962c183b1fed9 +5884a47c367f6ff1aff3ae1ef6894881c5a5e0b7 +1d39c9ca0672e7ad4c1f0959f9d58d2fcc7dc46b +e16f6441044fc2123e0cbdcbd8a5842ec3aae7a0 +6c6cc7989cac79450bf83b932ca82d390a37e17b +bc28ca3afb7f6656a0bf50038a5e383ee7f9b219 +57a491bee17af88f75c2cea8c109d93b1cdbc9a8 +f8586b25f6a4f1e30a54e58f45dd28aaf580bbc6 +e5df0ba0d97e5f8cfd748f09d6ed77b7bfc45471 +1b0469199bdaedfd452eea718268be7fd50db3c0 +015717e2b873b7a2ce433bd3be2328a782aa5d91 +3b3c66f85959f3393a3a9e87a29004b526f91b93 +874529665c1c326fc86fc0d0d6c3512fab087ab8 +7f2c983e1cfdb58b6f84eabe5ff6a16f143f39aa +0ea92cad5274f3939f09d6890da31a21b8481282 +489b5876698f9bb2d93b1b1d62d514148b31effd +faf25b09d9e78f2ff129e25b90f67930d2fc1c4f +df933596e7e9aa17f7e5cd6e1c850520f5b56f1b +9e4fbebcc8e497016563e46de4c64fa094edab2d +1557014378cc5a6234a9244fa60132955206fd27 +c5fbcf5f8d7b36bee54ac80d1027d0dccea2aa75 +cccbc5fe3ea5ae52426203f4485b11071fbe4b6e +5174a139c92c1238f9700d06e362dc628d81a0a9 +9dae9f5f1e2bf29f58d3f49b0c612063d883b8b3 +e282764e049523439bc8adaadc002a1420122830 +d8ae5044488248d5eb134aa7c0a15c813a2f8715 +06ea2783a2c11e7b171e2809c3211bb3091d894d +00ce8543f16f4357926eb6dc701ac6229142be80 +1f63b460a8506675ccacb4647941f07d391735e3 +a100c42a136da5ddfd09aa442543ec2190f24faf +636991d0c0f969968c790d490c82c1d2fa4e8047 +dd52f79a73eca18301db1569d517197160018dbb +e157b98640c7cfb94cae7e0faca3bcffc2dde990 +ad9e5eaf77bf7e19a926a43407c88386a8a1e58f +c5e67be03bb06a5d7885c55db1f016fbf2333fe3 +48eec32347494da781f26478fa488b28336afbd2 +c324b07a541a04698954ece94e5879ae7131c1c7 +4901631dac6a883c6ddd0d4e5e3edd08b10d7609 +cacbdbaa95317b45cf2100702bca92410fb43b9a +b4f686952a60bbadc7ed2250651d0d6af0959f4d +90e49c1ececd6296c6ec6109cea525a208c0626e +700808754884919916a5518e7ecfdabadef956d8 +0cd1a2eff9e0020ec1052a931f3863794d1a95d9 +51527ec1ec4264f7e24ef548bb049db07a89fc7f +ed4eeafbb6e2e73ff9fb9c03bd66bbb049b8aacd +d4475ea7ae70ad1a1f9374b88c68f01590a88d54 +5e1aacab576b8d8918da129097a9ac0816b6ead2 +fe6a299fc0020cd62156d4b7dd9c8dac358c69c5 +0047d9b89b9fa6be660c363961cf0af72fa62ecf +037c5e511fe2185d244049cae25a98f99b878787 +8730bd3fc87c8a7d44347267e1ff3c7a8674201b +47b8256da872722953693c4037d1b9e07caadcb1 +85aea18ae660b5edf7b6c1415f033cfcb15307f9 +132d5f8c2f2397a4600a42203f413dafdb6bcc37 +23ebd7a8027f12e722834d214113892fe8561fe1 +a19f641a80cb2841ca9f593e9be589dbc4b4ae7c +1e7db37e76c510d373c4404eea2b97508b367aca +16fa967d3cca66eef0f17b41fd8aaee6a1420fbc +9eedbe98c86ff2a9214c24c37f6524ce67fd129b +0342ae1d395ca82614f6d3b8fabb6a44403baf2a +777b89b3008e53374eb13fdee70db315cd61a703 +8b686776ef5cbd6ef9d5281c3136eded25ea35a4 +c90b42bcdb594638c5759ef5ef0773314d0a1379 +7134327be5c1bdcef7919ed735049a6bbfc457ec +e88a52e9a2fda971d34425bb80e42ad2d6623d68 +173c79626867e9f89d49be7dcbb0c2042c480553 +2513499348fa955d0e4b0970b08ba9e715e6316e +43bb10661360d9f35d921d493a1f94ac95df00e2 +6f55ab57cbfa414d57a8e9fb9a47f9bcd8c836d7 +6300b9556ec927a61371053fafe1a4045f5afb00 +f8b2e9bcfc76fede05f5e12f7b15f0d9c9d0add5 +b297b945f7610772434817181ad12067b2832565 +57a73d71a36ce212977607d3e94de6ef55521bfc +5fdf37e14bb3b66264a7e6868250c2084ac39054 +3059d4dd72af73b654077d9f72019c47edd47674 +333a41882c5ccd5f0c7f884f97d25449bdeec07b +7da4f65a00a8d96da2119de613ed7fbee2a28a0d +e14f0fa6a346afecbb1d5470aef5226a8cc33e57 +cf0a8b9c4870cc88254a757286140d9632e7b70c +b69fd5eaa99f84b62a49d7c7f48d8cee1227592a +1e3ed01faa77215a7c36308237280aaa58895532 +6c9bc14a3f2cfa50144607c820ebab5288f9571c +8e3c266a4f02093d57d563f32ba73d3ab4b5f208 +decde9bba6f9d3671bdf0af4fe6ff4bf28992d1d +9b7eb584ade2ce73dbfcda080935172c3857b758 +3bbc46ddafb61f68785c7e581817db952f99d93a +bbb83f0b2b2671980c06453fd243b1f2801a1cc4 +6c9460edaeb6c89692b71f51be7b7ee386f4f5c4 +b3072799248fae8fc16f910b642edb9c5acf8bac +696d39410fc3372d120a6e89695c1543ac2fc052 +c5c4fb31828107a5ded88627632e19e05b2c7e83 +9ce1c506a3a5d20b1bf254235bfae48af592d86c +fe66dad8a779ed928b1c2fc0c3accf594b042877 +f421de5be611f874a027392d5fee7e113dce4f54 +d492dc1cdaabdc52b0766bf4cba4bd73178325d0 +6348bc61b533705a229f2c2ddcff2bdd98849d07 +83b26cb97cb46516aa4fdee3bcbfa751d28c1233 +afac75f140a3e7d89877f03420e1bc64a8d8c6cd +171f6f2699dc27e77843318be2fefdfcd9e589fb +50c806f001d66e20f314777b9fa7fefa01dc6893 +bdbabc50ba6c87ded97ea2bbacd3605c59cd12d0 +9e32adbb5c543885b2c01a984bf1e4b80e8cec16 +7c08d81e119570792648fe95bbacddbb1d5f9ae2 +65e9ca22785f4a799cbcff6d95cbe1ce4b4a6bd2 +2948d6dea098bf722828b969838668f833c2cb00 +deb847b75710d600e5b0d3d5c77fa5166d80808a +05e5af5a6c884d2ade3d7acc766ad5380cb85b64 +cba41db327a241f992f9329b214d9070888255b8 +f6d335e82822ed8f2da56c1bcaddf7f99acd4997 +30308cc380a8176a5ec0e0bd2beed8b9c482ccf7 +8b6cd42c6226dea28c182a48a214d1c091b9b5bb +267917f5632a99bb51fc3fe516d8308e79d31ed1 +ba11eb354b9f3420ebb8608227062fb639a07496 +848b11615b67a3c49f76ebbcaa241a322d8014d8 +25290071c434638e2719a99784572deef44542ad +159f89c118645c0f9e23e453f01cede84abac795 +37637bea3a9a48c0d52d68d3f78f154f8249a009 +0a76bf848c72211f986a6cc5b1c13de820b861dd +358fe779cbb2681666ae5ab23a19662db21a2c46 +c44e734dca64a15fae92255a5d848c04adaad2fa +8add59d77dd621be57059229f378822e4b707318 +922c49a1389531d9fba30168257c466bd413f625 +df0825046acc7cb496c47666e36af18118beb030 +c23bf06492dddacfc0eece3d4dd12cce81496dd0 +3eec29ed3aa1c8eb293a7a7a6be356fc014f8813 +a7e80449c0811b361cdaea39b6bab78ca5fbf668 +5e8e0b3d7f6055e326bda61e60712b530e8920f0 +a5edd191be93aff8f9c0f60f04e711e2e78ecc77 +515200298b555845696a07ae2bc0a84a5dd02ae4 +e8a3882e20f0ffeeb9b3964c2c09d8aa5eb53fd4 +c545a7aeb1d559377933c7b2e6edc2d4a37b33fb +df669230cf2001dd869e897bb4f2d9c46f9accd9 +56a0fbf8365343d73cdff2b0a0e16542294d7577 +196b4599201dbce3e0317e9b98753fa6a244b82d +cf5bb048e80d4cde8828787b266b7f5f2e3b6d7b +b94d0c7af11bd91dad4f180ce2a2ffa09e4b5668 +792d0d8d512cf8ddca200317b74ce550c1a1a428 +767ee2e3a1082468b4e2248bac3ef8bd54bb2ddb +31db3dd874dfbba88616c96a5767e2c9861d9a7a +018fd9620293582f0ce43d344ac3110e19c4dedc +801aaac2b39564aa14009785146ba26d2506fb53 +121d47afe3e67ff7f94d26e08a39573dccf652aa +af7fba3af788e91a460582351d40f8f5e2118760 +8f1c28a609b203e0d0a844d9cc5ada9eb9160a5e +8319c4e906e6df5f2048e7c048942fde285a93a2 +66be456d93a66526322b7f36fd734a8dbd5e5524 +c006ab29ceec9274dc85a0de7f7d0502021a4b87 +1220af5e6d1072ea306f6ecaaa7effe3d386c379 +14ba286556faad794f288ef38493c540382897cb +784a21d35466736a7a372364498ed94482a12a2a +4ad59042b359f473d5888ecee0c9288dcf98f1c9 +fee16b15fa3425871670239c25d4e61ae961e0c5 +216f4ca9e7ccb1f0fcb9bab0f9940992a87ae55f +2d0bdb2089644f5904629413423cdc897911b081 +50c502f54abd9eb15c8ddca013f0fdfae3d132a9 +c840ab0231bc29057172179f005001c9ab299554 +aab5e48d422d396aec09bd6389de68613b19def5 diff --git a/contrib/verify-commits/trusted-keys b/contrib/verify-commits/trusted-keys index 5ca65e7b0d..eeafcdf205 100644 --- a/contrib/verify-commits/trusted-keys +++ b/contrib/verify-commits/trusted-keys @@ -1,4 +1,3 @@ -71A3B16735405025D447E8F274810B012346C9A6 B8B3F1C0E58C15DB6A81D30C3648A882F4316B9B E777299FC265DD04793070EB944D35F9AC3DB76A D1DBF2C4B96F2DEBF4C16654410108112E7EA81F From 5fffff54e9fcf154c722dc421025a567fa0c5c97 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Fri, 3 Feb 2023 11:41:17 +0100 Subject: [PATCH 30/52] ci: Cache stuff in volumes, not host folders --- ci/test/00_setup_env.sh | 7 ++++--- ci/test/04_install.sh | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ci/test/00_setup_env.sh b/ci/test/00_setup_env.sh index 07c20f632d..ab830b8ec0 100755 --- a/ci/test/00_setup_env.sh +++ b/ci/test/00_setup_env.sh @@ -8,11 +8,10 @@ export LC_ALL=C.UTF-8 # The root dir. # The ci system copies this folder. -# This is where the depends build is done. BASE_ROOT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )"/../../ >/dev/null 2>&1 && pwd ) export BASE_ROOT_DIR # The depends dir. -# This folder exists on the ci host and ci guest. Changes are propagated back and forth. +# This folder exists only on the ci guest, and on the ci host as a volume. export DEPENDS_DIR=${DEPENDS_DIR:-$BASE_ROOT_DIR/depends} # A folder for the ci system to put temporary files (ccache, datadirs for tests, ...) # This folder only exists on the ci host. @@ -58,12 +57,14 @@ export CCACHE_SIZE=${CCACHE_SIZE:-100M} export CCACHE_TEMPDIR=${CCACHE_TEMPDIR:-/tmp/.ccache-temp} export CCACHE_COMPRESS=${CCACHE_COMPRESS:-1} # The cache dir. -# This folder exists on the ci host and ci guest. Changes are propagated back and forth. +# This folder exists only on the ci guest, and on the ci host as a volume. export CCACHE_DIR=${CCACHE_DIR:-$BASE_SCRATCH_DIR/.ccache} # Folder where the build result is put (bin and lib). export BASE_OUTDIR=${BASE_OUTDIR:-$BASE_SCRATCH_DIR/out/$HOST} # Folder where the build is done (dist and out-of-tree build). export BASE_BUILD_DIR=${BASE_BUILD_DIR:-$BASE_SCRATCH_DIR/build} +# The folder for previous release binaries. +# This folder exists only on the ci guest, and on the ci host as a volume. export PREVIOUS_RELEASES_DIR=${PREVIOUS_RELEASES_DIR:-$BASE_ROOT_DIR/releases/$HOST} export SDK_URL=${SDK_URL:-https://bitcoincore.org/depends-sources/sdks} export CI_BASE_PACKAGES=${CI_BASE_PACKAGES:-build-essential libtool autotools-dev automake pkg-config bsdmainutils curl ca-certificates ccache python3 rsync git procps bison} diff --git a/ci/test/04_install.sh b/ci/test/04_install.sh index 05bef79a3d..62bc3a963d 100755 --- a/ci/test/04_install.sh +++ b/ci/test/04_install.sh @@ -39,6 +39,9 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then --build-arg "FILE_ENV=${FILE_ENV}" \ --tag="${CONTAINER_NAME}" \ "${BASE_ROOT_DIR}" + docker volume create "${CONTAINER_NAME}_ccache" || true + docker volume create "${CONTAINER_NAME}_depends" || true + docker volume create "${CONTAINER_NAME}_previous_releases" || true if [ -n "${RESTART_CI_DOCKER_BEFORE_RUN}" ] ; then echo "Restart docker before run to stop and clear all containers started with --rm" @@ -48,9 +51,9 @@ if [ -z "$DANGER_RUN_CI_ON_HOST" ]; then # shellcheck disable=SC2086 CI_CONTAINER_ID=$(docker run $CI_CONTAINER_CAP --rm --interactive --detach --tty \ --mount type=bind,src=$BASE_ROOT_DIR,dst=/ro_base,readonly \ - --mount type=bind,src=$CCACHE_DIR,dst=$CCACHE_DIR \ - --mount type=bind,src=$DEPENDS_DIR,dst=$DEPENDS_DIR \ - --mount type=bind,src=$PREVIOUS_RELEASES_DIR,dst=$PREVIOUS_RELEASES_DIR \ + --mount "type=volume,src=${CONTAINER_NAME}_ccache,dst=$CCACHE_DIR" \ + --mount "type=volume,src=${CONTAINER_NAME}_depends,dst=$DEPENDS_DIR" \ + --mount "type=volume,src=${CONTAINER_NAME}_previous_releases,dst=$PREVIOUS_RELEASES_DIR" \ -w $BASE_ROOT_DIR \ --env-file /tmp/env \ --name $CONTAINER_NAME \ From 772671245d50d94fd5087deb2542854604eba174 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Sun, 25 Sep 2022 14:13:38 +0200 Subject: [PATCH 31/52] test: p2p: check that headers message with invalid proof-of-work disconnects peer --- test/functional/p2p_invalid_messages.py | 34 ++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index 3109ad2b56..ea4999a965 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -13,11 +13,12 @@ MAX_HEADERS_RESULTS, MAX_INV_SIZE, MAX_PROTOCOL_MESSAGE_LENGTH, + MSG_TX, + from_hex, msg_getdata, msg_headers, msg_inv, msg_ping, - MSG_TX, msg_version, ser_string, ) @@ -73,6 +74,7 @@ def run_test(self): self.test_oversized_inv_msg() self.test_oversized_getdata_msg() self.test_oversized_headers_msg() + self.test_invalid_pow_headers_msg() self.test_resource_exhaustion() def test_buffer(self): @@ -248,6 +250,36 @@ def test_oversized_headers_msg(self): size = MAX_HEADERS_RESULTS + 1 self.test_oversized_msg(msg_headers([CBlockHeader()] * size), size) + def test_invalid_pow_headers_msg(self): + self.log.info("Test headers message with invalid proof-of-work is logged as misbehaving and disconnects peer") + blockheader_tip_hash = self.nodes[0].getbestblockhash() + blockheader_tip = from_hex(CBlockHeader(), self.nodes[0].getblockheader(blockheader_tip_hash, False)) + + # send valid headers message first + assert_equal(self.nodes[0].getblockchaininfo()['headers'], 0) + blockheader = CBlockHeader() + blockheader.hashPrevBlock = int(blockheader_tip_hash, 16) + blockheader.nTime = int(time.time()) + blockheader.nBits = blockheader_tip.nBits + blockheader.rehash() + while not blockheader.hash.startswith('0'): + blockheader.nNonce += 1 + blockheader.rehash() + peer = self.nodes[0].add_p2p_connection(P2PInterface()) + peer.send_and_ping(msg_headers([blockheader])) + assert_equal(self.nodes[0].getblockchaininfo()['headers'], 1) + chaintips = self.nodes[0].getchaintips() + assert_equal(chaintips[0]['status'], 'headers-only') + assert_equal(chaintips[0]['hash'], blockheader.hash) + + # invalidate PoW + while not blockheader.hash.startswith('f'): + blockheader.nNonce += 1 + blockheader.rehash() + with self.nodes[0].assert_debug_log(['Misbehaving', 'header with invalid proof of work']): + peer.send_message(msg_headers([blockheader])) + peer.wait_for_disconnect() + def test_resource_exhaustion(self): self.log.info("Test node stays up despite many large junk messages") conn = self.nodes[0].add_p2p_connection(P2PDataStore()) From fa8e92c022057adcb8b98647bde626ed9c054df2 Mon Sep 17 00:00:00 2001 From: "721217.xyz" <*~=`'#}+{/-|&$^_@721217.xyz> Date: Fri, 10 Feb 2023 17:08:05 +0100 Subject: [PATCH 32/52] doc: Update ci docs --- ci/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ci/README.md b/ci/README.md index 3c5f04c39e..de798607df 100644 --- a/ci/README.md +++ b/ci/README.md @@ -8,8 +8,7 @@ Be aware that the tests will be built and run in-place, so please run at your ow If the repository is not a fresh git clone, you might have to clean files from previous builds or test runs first. The ci needs to perform various sysadmin tasks such as installing packages or writing to the user's home directory. -While most of the actions are done inside a docker container, this is not possible for all. Thus, cache directories, -such as the depends cache, previous release binaries, or ccache, are mounted as read-write into the docker container. While it should be fine to run +While it should be fine to run the ci system locally on you development box, the ci scripts can generally be assumed to have received less review and testing compared to other parts of the codebase. If you want to keep the work tree clean, you might want to run the ci system in a virtual machine with a Linux operating system of your choice. From 6ada37d44cce7fa3a729de72cede4c1f3bc675ce Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 10 Feb 2023 11:36:06 -0500 Subject: [PATCH 33/52] verify-commits: Bump trusted git root to after most recent laanwj merge To prepare for the removal of laanwj's key from trusted key, the trusted git root needs to be newer than the most recent merge commit signed by his key. --- contrib/verify-commits/trusted-git-root | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/verify-commits/trusted-git-root b/contrib/verify-commits/trusted-git-root index 1c42195961..efb6b9f7b4 100644 --- a/contrib/verify-commits/trusted-git-root +++ b/contrib/verify-commits/trusted-git-root @@ -1 +1 @@ -577bd51a4b8de066466a445192c1c653872657e2 +8ef096d4f8e08ac691502e3fd34721a8bdfa9044 From 3a11adc7004d21b3dfe028b190d83add31691c55 Mon Sep 17 00:00:00 2001 From: John Moffett Date: Fri, 10 Feb 2023 16:13:40 -0500 Subject: [PATCH 34/52] Zero out wallet master key upon lock When an encrypted wallet is locked (for instance via the RPC `walletlock`), the docs indicate that the key is removed from memory. However, the vector (with a secure allocator) is merely cleared. This allows the key to persist indefinitely in memory. Instead, manually fill the bytes with zeroes before clearing. --- src/wallet/wallet.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5a92dbe428..b709bd9650 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -26,6 +26,7 @@ #include