aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.clang-format1
-rw-r--r--src/Makefile.am2
-rw-r--r--src/Makefile.test.include10
-rw-r--r--src/qt/forms/signverifymessagedialog.ui18
-rw-r--r--src/script/descriptor.cpp66
-rw-r--r--src/script/descriptor.h78
-rw-r--r--src/test/fs_tests.cpp2
-rw-r--r--src/test/fuzz/FuzzedDataProvider.h245
-rw-r--r--src/test/fuzz/eval_script.cpp30
-rw-r--r--src/test/util_tests.cpp124
-rw-r--r--src/util/spanparsing.cpp67
-rw-r--r--src/util/spanparsing.h50
-rw-r--r--src/wallet/test/wallet_tests.cpp78
13 files changed, 672 insertions, 99 deletions
diff --git a/src/.clang-format b/src/.clang-format
index 38e19edf2c..aae039dd77 100644
--- a/src/.clang-format
+++ b/src/.clang-format
@@ -5,6 +5,7 @@ AlignEscapedNewlinesLeft: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
diff --git a/src/Makefile.am b/src/Makefile.am
index 84254e45d1..eec84122ae 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -208,6 +208,7 @@ BITCOIN_CORE_H = \
util/bytevectorhash.h \
util/error.h \
util/fees.h \
+ util/spanparsing.h \
util/system.h \
util/macros.h \
util/memory.h \
@@ -505,6 +506,7 @@ libbitcoin_util_a_SOURCES = \
util/moneystr.cpp \
util/rbf.cpp \
util/threadnames.cpp \
+ util/spanparsing.cpp \
util/strencodings.cpp \
util/string.cpp \
util/time.cpp \
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 48df50d100..b8957e52bd 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -17,6 +17,7 @@ FUZZ_TARGETS = \
test/fuzz/bloomfilter_deserialize \
test/fuzz/coins_deserialize \
test/fuzz/diskblockindex_deserialize \
+ test/fuzz/eval_script \
test/fuzz/inv_deserialize \
test/fuzz/messageheader_deserialize \
test/fuzz/netaddr_deserialize \
@@ -59,7 +60,8 @@ FUZZ_SUITE = \
test/setup_common.h \
test/setup_common.cpp \
test/fuzz/fuzz.cpp \
- test/fuzz/fuzz.h
+ test/fuzz/fuzz.h \
+ test/fuzz/FuzzedDataProvider.h
FUZZ_SUITE_LD_COMMON = \
$(LIBBITCOIN_SERVER) \
@@ -298,6 +300,12 @@ test_fuzz_diskblockindex_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_diskblockindex_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_diskblockindex_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
+test_fuzz_eval_script_SOURCES = $(FUZZ_SUITE) test/fuzz/eval_script.cpp
+test_fuzz_eval_script_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
+test_fuzz_eval_script_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
+test_fuzz_eval_script_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
+test_fuzz_eval_script_LDADD = $(FUZZ_SUITE_LD_COMMON)
+
test_fuzz_txoutcompressor_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp
test_fuzz_txoutcompressor_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DTXOUTCOMPRESSOR_DESERIALIZE=1
test_fuzz_txoutcompressor_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
diff --git a/src/qt/forms/signverifymessagedialog.ui b/src/qt/forms/signverifymessagedialog.ui
index c9ddd757c1..8a7bdfdbc6 100644
--- a/src/qt/forms/signverifymessagedialog.ui
+++ b/src/qt/forms/signverifymessagedialog.ui
@@ -285,10 +285,24 @@
</layout>
</item>
<item>
- <widget class="QPlainTextEdit" name="messageIn_VM"/>
+ <widget class="QPlainTextEdit" name="messageIn_VM">
+ <property name="toolTip">
+ <string>The signed message to verify</string>
+ </property>
+ <property name="placeholderText">
+ <string>The signed message to verify</string>
+ </property>
+ </widget>
</item>
<item>
- <widget class="QValidatedLineEdit" name="signatureIn_VM"/>
+ <widget class="QValidatedLineEdit" name="signatureIn_VM">
+ <property name="toolTip">
+ <string>The signature given when the message was signed</string>
+ </property>
+ <property name="placeholderText">
+ <string>The signature given when the message was signed</string>
+ </property>
+ </widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2_VM">
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index b223349eb1..ed1bd4cda9 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -11,6 +11,7 @@
#include <span.h>
#include <util/bip32.h>
+#include <util/spanparsing.h>
#include <util/system.h>
#include <util/strencodings.h>
@@ -640,63 +641,6 @@ enum class ParseScriptContext {
P2WSH,
};
-/** Parse a constant. If successful, sp is updated to skip the constant and return true. */
-bool Const(const std::string& str, Span<const char>& sp)
-{
- if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) {
- sp = sp.subspan(str.size());
- return true;
- }
- return false;
-}
-
-/** Parse a function call. If successful, sp is updated to be the function's argument(s). */
-bool Func(const std::string& str, Span<const char>& sp)
-{
- if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) {
- sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2);
- return true;
- }
- return false;
-}
-
-/** Return the expression that sp begins with, and update sp to skip it. */
-Span<const char> Expr(Span<const char>& sp)
-{
- int level = 0;
- auto it = sp.begin();
- while (it != sp.end()) {
- if (*it == '(') {
- ++level;
- } else if (level && *it == ')') {
- --level;
- } else if (level == 0 && (*it == ')' || *it == ',')) {
- break;
- }
- ++it;
- }
- Span<const char> ret = sp.first(it - sp.begin());
- sp = sp.subspan(it - sp.begin());
- return ret;
-}
-
-/** Split a string on every instance of sep, returning a vector. */
-std::vector<Span<const char>> Split(const Span<const char>& sp, char sep)
-{
- std::vector<Span<const char>> ret;
- auto it = sp.begin();
- auto start = it;
- while (it != sp.end()) {
- if (*it == sep) {
- ret.emplace_back(start, it);
- start = it + 1;
- }
- ++it;
- }
- ret.emplace_back(start, it);
- return ret;
-}
-
/** Parse a key path, being passed a split list of elements (the first element is ignored). */
NODISCARD bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out, std::string& error)
{
@@ -723,6 +667,8 @@ NODISCARD bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath&
/** Parse a public key that excludes origin information. */
std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error)
{
+ using namespace spanparsing;
+
auto split = Split(sp, '/');
std::string str(split[0].begin(), split[0].end());
if (str.size() == 0) {
@@ -782,6 +728,8 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char>& sp, boo
/** Parse a public key including origin information (if enabled). */
std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error)
{
+ using namespace spanparsing;
+
auto origin_split = Split(sp, ']');
if (origin_split.size() > 2) {
error = "Multiple ']' characters found for a single pubkey";
@@ -816,6 +764,8 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool per
/** Parse a script in a particular context. */
std::unique_ptr<DescriptorImpl> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
{
+ using namespace spanparsing;
+
auto expr = Expr(sp);
bool sorted_multi = false;
if (Func("pk", expr)) {
@@ -1012,6 +962,8 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
/** Check a descriptor checksum, and update desc to be the checksum-less part. */
bool CheckChecksum(Span<const char>& sp, bool require_checksum, std::string& error, std::string* out_checksum = nullptr)
{
+ using namespace spanparsing;
+
auto check_split = Split(sp, '#');
if (check_split.size() > 2) {
error = "Multiple '#' symbols";
diff --git a/src/script/descriptor.h b/src/script/descriptor.h
index 0195ca0939..5a1b55259a 100644
--- a/src/script/descriptor.h
+++ b/src/script/descriptor.h
@@ -11,22 +11,24 @@
#include <vector>
-// Descriptors are strings that describe a set of scriptPubKeys, together with
-// all information necessary to solve them. By combining all information into
-// one, they avoid the need to separately import keys and scripts.
-//
-// Descriptors may be ranged, which occurs when the public keys inside are
-// specified in the form of HD chains (xpubs).
-//
-// Descriptors always represent public information - public keys and scripts -
-// but in cases where private keys need to be conveyed along with a descriptor,
-// they can be included inside by changing public keys to private keys (WIF
-// format), and changing xpubs by xprvs.
-//
-// Reference documentation about the descriptor language can be found in
-// doc/descriptors.md.
-
-/** Interface for parsed descriptor objects. */
+
+/** \brief Interface for parsed descriptor objects.
+ *
+ * Descriptors are strings that describe a set of scriptPubKeys, together with
+ * all information necessary to solve them. By combining all information into
+ * one, they avoid the need to separately import keys and scripts.
+ *
+ * Descriptors may be ranged, which occurs when the public keys inside are
+ * specified in the form of HD chains (xpubs).
+ *
+ * Descriptors always represent public information - public keys and scripts -
+ * but in cases where private keys need to be conveyed along with a descriptor,
+ * they can be included inside by changing public keys to private keys (WIF
+ * format), and changing xpubs by xprvs.
+ *
+ * Reference documentation about the descriptor language can be found in
+ * doc/descriptors.md.
+ */
struct Descriptor {
virtual ~Descriptor() = default;
@@ -45,51 +47,51 @@ struct Descriptor {
/** Expand a descriptor at a specified position.
*
- * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored.
- * provider: the provider to query for private keys in case of hardened derivation.
- * output_scripts: the expanded scriptPubKeys will be put here.
- * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider).
- * cache: vector which will be overwritten with cache data necessary to evaluate the descriptor at this point without access to private keys.
+ * @param[in] pos: The position at which to expand the descriptor. If IsRange() is false, this is ignored.
+ * @param[in] provider: The provider to query for private keys in case of hardened derivation.
+ * @param[out] output_scripts: The expanded scriptPubKeys.
+ * @param[out] out: Scripts and public keys necessary for solving the expanded scriptPubKeys (may be equal to `provider`).
+ * @param[out] cache: Cache data necessary to evaluate the descriptor at this point without access to private keys.
*/
virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache = nullptr) const = 0;
/** Expand a descriptor at a specified position using cached expansion data.
*
- * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored.
- * cache: vector from which cached expansion data will be read.
- * output_scripts: the expanded scriptPubKeys will be put here.
- * out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider).
+ * @param[in] pos: The position at which to expand the descriptor. If IsRange() is false, this is ignored.
+ * @param[in] cache: Cached expansion data.
+ * @param[out] output_scripts: The expanded scriptPubKeys.
+ * @param[out] out: Scripts and public keys necessary for solving the expanded scriptPubKeys (may be equal to `provider`).
*/
virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0;
/** Expand the private key for a descriptor at a specified position, if possible.
*
- * pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored.
- * provider: the provider to query for the private keys.
- * out: any private keys available for the specified pos will be placed here.
+ * @param[in] pos: The position at which to expand the descriptor. If IsRange() is false, this is ignored.
+ * @param[in] provider: The provider to query for the private keys.
+ * @param[out] out: Any private keys available for the specified `pos`.
*/
virtual void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const = 0;
};
-/** Parse a descriptor string. Included private keys are put in out.
+/** Parse a `descriptor` string. Included private keys are put in `out`.
*
- * If the descriptor has a checksum, it must be valid. If require_checksum
+ * If the descriptor has a checksum, it must be valid. If `require_checksum`
* is set, the checksum is mandatory - otherwise it is optional.
*
* If a parse error occurs, or the checksum is missing/invalid, or anything
- * else is wrong, nullptr is returned.
+ * else is wrong, `nullptr` is returned.
*/
std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, std::string& error, bool require_checksum = false);
-/** Get the checksum for a descriptor.
+/** Get the checksum for a `descriptor`.
*
- * If it already has one, and it is correct, return the checksum in the input.
- * If it already has one that is wrong, return "".
- * If it does not already have one, return the checksum that would need to be added.
+ * - If it already has one, and it is correct, return the checksum in the input.
+ * - If it already has one that is wrong, return "".
+ * - If it does not already have one, return the checksum that would need to be added.
*/
std::string GetDescriptorChecksum(const std::string& descriptor);
-/** Find a descriptor for the specified script, using information from provider where possible.
+/** Find a descriptor for the specified `script`, using information from `provider` where possible.
*
* A non-ranged descriptor which only generates the specified script will be returned in all
* circumstances.
@@ -98,9 +100,9 @@ std::string GetDescriptorChecksum(const std::string& descriptor);
* descriptor.
*
* - If all information for solving `script` is present in `provider`, a descriptor will be returned
- * which is `IsSolvable()` and encapsulates said information.
+ * which is IsSolvable() and encapsulates said information.
* - Failing that, if `script` corresponds to a known address type, an "addr()" descriptor will be
- * returned (which is not `IsSolvable()`).
+ * returned (which is not IsSolvable()).
* - Failing that, a "raw()" descriptor is returned.
*/
std::unique_ptr<Descriptor> InferDescriptor(const CScript& script, const SigningProvider& provider);
diff --git a/src/test/fs_tests.cpp b/src/test/fs_tests.cpp
index 6d5a6641f0..b504a3cbb1 100644
--- a/src/test/fs_tests.cpp
+++ b/src/test/fs_tests.cpp
@@ -15,7 +15,7 @@ BOOST_AUTO_TEST_CASE(fsbridge_fstream)
fs::path tmpfolder = GetDataDir();
// tmpfile1 should be the same as tmpfile2
fs::path tmpfile1 = tmpfolder / "fs_tests_₿_🏃";
- fs::path tmpfile2 = tmpfolder / L"fs_tests_₿_🏃";
+ fs::path tmpfile2 = tmpfolder / "fs_tests_₿_🏃";
{
fsbridge::ofstream file(tmpfile1);
file << "bitcoin";
diff --git a/src/test/fuzz/FuzzedDataProvider.h b/src/test/fuzz/FuzzedDataProvider.h
new file mode 100644
index 0000000000..1b5b4bb012
--- /dev/null
+++ b/src/test/fuzz/FuzzedDataProvider.h
@@ -0,0 +1,245 @@
+//===- FuzzedDataProvider.h - Utility header for fuzz targets ---*- C++ -* ===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// A single header library providing an utility class to break up an array of
+// bytes. Whenever run on the same input, provides the same output, as long as
+// its methods are called in the same order, with the same arguments.
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
+#define LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
+
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <cstring>
+#include <initializer_list>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+class FuzzedDataProvider {
+public:
+ // |data| is an array of length |size| that the FuzzedDataProvider wraps to
+ // provide more granular access. |data| must outlive the FuzzedDataProvider.
+ FuzzedDataProvider(const uint8_t *data, size_t size)
+ : data_ptr_(data), remaining_bytes_(size) {}
+ ~FuzzedDataProvider() = default;
+
+ // Returns a std::vector containing |num_bytes| of input data. If fewer than
+ // |num_bytes| of data remain, returns a shorter std::vector containing all
+ // of the data that's left. Can be used with any byte sized type, such as
+ // char, unsigned char, uint8_t, etc.
+ template <typename T> std::vector<T> ConsumeBytes(size_t num_bytes) {
+ num_bytes = std::min(num_bytes, remaining_bytes_);
+ return ConsumeBytes<T>(num_bytes, num_bytes);
+ }
+
+ // Similar to |ConsumeBytes|, but also appends the terminator value at the end
+ // of the resulting vector. Useful, when a mutable null-terminated C-string is
+ // needed, for example. But that is a rare case. Better avoid it, if possible,
+ // and prefer using |ConsumeBytes| or |ConsumeBytesAsString| methods.
+ template <typename T>
+ std::vector<T> ConsumeBytesWithTerminator(size_t num_bytes,
+ T terminator = 0) {
+ num_bytes = std::min(num_bytes, remaining_bytes_);
+ std::vector<T> result = ConsumeBytes<T>(num_bytes + 1, num_bytes);
+ result.back() = terminator;
+ return result;
+ }
+
+ // Returns a std::string containing |num_bytes| of input data. Using this and
+ // |.c_str()| on the resulting string is the best way to get an immutable
+ // null-terminated C string. If fewer than |num_bytes| of data remain, returns
+ // a shorter std::string containing all of the data that's left.
+ std::string ConsumeBytesAsString(size_t num_bytes) {
+ static_assert(sizeof(std::string::value_type) == sizeof(uint8_t),
+ "ConsumeBytesAsString cannot convert the data to a string.");
+
+ num_bytes = std::min(num_bytes, remaining_bytes_);
+ std::string result(
+ reinterpret_cast<const std::string::value_type *>(data_ptr_),
+ num_bytes);
+ Advance(num_bytes);
+ return result;
+ }
+
+ // Returns a number in the range [min, max] by consuming bytes from the
+ // input data. The value might not be uniformly distributed in the given
+ // range. If there's no input data left, always returns |min|. |min| must
+ // be less than or equal to |max|.
+ template <typename T> T ConsumeIntegralInRange(T min, T max) {
+ static_assert(std::is_integral<T>::value, "An integral type is required.");
+ static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
+
+ if (min > max)
+ abort();
+
+ // Use the biggest type possible to hold the range and the result.
+ uint64_t range = static_cast<uint64_t>(max) - min;
+ uint64_t result = 0;
+ size_t offset = 0;
+
+ while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 &&
+ remaining_bytes_ != 0) {
+ // Pull bytes off the end of the seed data. Experimentally, this seems to
+ // allow the fuzzer to more easily explore the input space. This makes
+ // sense, since it works by modifying inputs that caused new code to run,
+ // and this data is often used to encode length of data read by
+ // |ConsumeBytes|. Separating out read lengths makes it easier modify the
+ // contents of the data that is actually read.
+ --remaining_bytes_;
+ result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_];
+ offset += CHAR_BIT;
+ }
+
+ // Avoid division by 0, in case |range + 1| results in overflow.
+ if (range != std::numeric_limits<decltype(range)>::max())
+ result = result % (range + 1);
+
+ return static_cast<T>(min + result);
+ }
+
+ // Returns a std::string of length from 0 to |max_length|. When it runs out of
+ // input data, returns what remains of the input. Designed to be more stable
+ // with respect to a fuzzer inserting characters than just picking a random
+ // length and then consuming that many bytes with |ConsumeBytes|.
+ std::string ConsumeRandomLengthString(size_t max_length) {
+ // Reads bytes from the start of |data_ptr_|. Maps "\\" to "\", and maps "\"
+ // followed by anything else to the end of the string. As a result of this
+ // logic, a fuzzer can insert characters into the string, and the string
+ // will be lengthened to include those new characters, resulting in a more
+ // stable fuzzer than picking the length of a string independently from
+ // picking its contents.
+ std::string result;
+
+ // Reserve the anticipated capaticity to prevent several reallocations.
+ result.reserve(std::min(max_length, remaining_bytes_));
+ for (size_t i = 0; i < max_length && remaining_bytes_ != 0; ++i) {
+ char next = ConvertUnsignedToSigned<char>(data_ptr_[0]);
+ Advance(1);
+ if (next == '\\' && remaining_bytes_ != 0) {
+ next = ConvertUnsignedToSigned<char>(data_ptr_[0]);
+ Advance(1);
+ if (next != '\\')
+ break;
+ }
+ result += next;
+ }
+
+ result.shrink_to_fit();
+ return result;
+ }
+
+ // Returns a std::vector containing all remaining bytes of the input data.
+ template <typename T> std::vector<T> ConsumeRemainingBytes() {
+ return ConsumeBytes<T>(remaining_bytes_);
+ }
+
+ // Prefer using |ConsumeRemainingBytes| unless you actually need a std::string
+ // object.
+ // Returns a std::vector containing all remaining bytes of the input data.
+ std::string ConsumeRemainingBytesAsString() {
+ return ConsumeBytesAsString(remaining_bytes_);
+ }
+
+ // Returns a number in the range [Type's min, Type's max]. The value might
+ // not be uniformly distributed in the given range. If there's no input data
+ // left, always returns |min|.
+ template <typename T> T ConsumeIntegral() {
+ return ConsumeIntegralInRange(std::numeric_limits<T>::min(),
+ std::numeric_limits<T>::max());
+ }
+
+ // Reads one byte and returns a bool, or false when no data remains.
+ bool ConsumeBool() { return 1 & ConsumeIntegral<uint8_t>(); }
+
+ // Returns a copy of a value selected from a fixed-size |array|.
+ template <typename T, size_t size>
+ T PickValueInArray(const T (&array)[size]) {
+ static_assert(size > 0, "The array must be non empty.");
+ return array[ConsumeIntegralInRange<size_t>(0, size - 1)];
+ }
+
+ template <typename T>
+ T PickValueInArray(std::initializer_list<const T> list) {
+ // static_assert(list.size() > 0, "The array must be non empty.");
+ return *(list.begin() + ConsumeIntegralInRange<size_t>(0, list.size() - 1));
+ }
+
+ // Return an enum value. The enum must start at 0 and be contiguous. It must
+ // also contain |kMaxValue| aliased to its largest (inclusive) value. Such as:
+ // enum class Foo { SomeValue, OtherValue, kMaxValue = OtherValue };
+ template <typename T> T ConsumeEnum() {
+ static_assert(std::is_enum<T>::value, "|T| must be an enum type.");
+ return static_cast<T>(ConsumeIntegralInRange<uint32_t>(
+ 0, static_cast<uint32_t>(T::kMaxValue)));
+ }
+
+ // Reports the remaining bytes available for fuzzed input.
+ size_t remaining_bytes() { return remaining_bytes_; }
+
+private:
+ FuzzedDataProvider(const FuzzedDataProvider &) = delete;
+ FuzzedDataProvider &operator=(const FuzzedDataProvider &) = delete;
+
+ void Advance(size_t num_bytes) {
+ if (num_bytes > remaining_bytes_)
+ abort();
+
+ data_ptr_ += num_bytes;
+ remaining_bytes_ -= num_bytes;
+ }
+
+ template <typename T>
+ std::vector<T> ConsumeBytes(size_t size, size_t num_bytes_to_consume) {
+ static_assert(sizeof(T) == sizeof(uint8_t), "Incompatible data type.");
+
+ // The point of using the size-based constructor below is to increase the
+ // odds of having a vector object with capacity being equal to the length.
+ // That part is always implementation specific, but at least both libc++ and
+ // libstdc++ allocate the requested number of bytes in that constructor,
+ // which seems to be a natural choice for other implementations as well.
+ // To increase the odds even more, we also call |shrink_to_fit| below.
+ std::vector<T> result(size);
+ std::memcpy(result.data(), data_ptr_, num_bytes_to_consume);
+ Advance(num_bytes_to_consume);
+
+ // Even though |shrink_to_fit| is also implementation specific, we expect it
+ // to provide an additional assurance in case vector's constructor allocated
+ // a buffer which is larger than the actual amount of data we put inside it.
+ result.shrink_to_fit();
+ return result;
+ }
+
+ template <typename TS, typename TU> TS ConvertUnsignedToSigned(TU value) {
+ static_assert(sizeof(TS) == sizeof(TU), "Incompatible data types.");
+ static_assert(!std::numeric_limits<TU>::is_signed,
+ "Source type must be unsigned.");
+
+ // TODO(Dor1s): change to `if constexpr` once C++17 becomes mainstream.
+ if (std::numeric_limits<TS>::is_modulo)
+ return static_cast<TS>(value);
+
+ // Avoid using implementation-defined unsigned to signer conversions.
+ // To learn more, see https://stackoverflow.com/questions/13150449.
+ if (value <= std::numeric_limits<TS>::max())
+ return static_cast<TS>(value);
+ else {
+ constexpr auto TS_min = std::numeric_limits<TS>::min();
+ return TS_min + static_cast<char>(value - TS_min);
+ }
+ }
+
+ const uint8_t *data_ptr_;
+ size_t remaining_bytes_;
+};
+
+#endif // LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
diff --git a/src/test/fuzz/eval_script.cpp b/src/test/fuzz/eval_script.cpp
new file mode 100644
index 0000000000..9444cd489e
--- /dev/null
+++ b/src/test/fuzz/eval_script.cpp
@@ -0,0 +1,30 @@
+// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <script/interpreter.h>
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+
+#include <limits>
+
+void test_one_input(const std::vector<uint8_t>& buffer)
+{
+ FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
+ const unsigned int flags = fuzzed_data_provider.ConsumeIntegral<unsigned int>();
+ const std::vector<uint8_t> script_bytes = [&] {
+ if (fuzzed_data_provider.remaining_bytes() != 0) {
+ return fuzzed_data_provider.ConsumeRemainingBytes<uint8_t>();
+ } else {
+ // Avoid UBSan warning:
+ // test/fuzz/FuzzedDataProvider.h:212:17: runtime error: null pointer passed as argument 1, which is declared to never be null
+ // /usr/include/string.h:43:28: note: nonnull attribute specified here
+ return std::vector<uint8_t>();
+ }
+ }();
+ const CScript script(script_bytes.begin(), script_bytes.end());
+ for (const auto sig_version : {SigVersion::BASE, SigVersion::WITNESS_V0}) {
+ std::vector<std::vector<unsigned char>> stack;
+ (void)EvalScript(stack, script, flags, BaseSignatureChecker(), sig_version, nullptr);
+ }
+}
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index d0cd4b0a03..31a66b6fa9 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -12,6 +12,7 @@
#include <util/strencodings.h>
#include <util/string.h>
#include <util/time.h>
+#include <util/spanparsing.h>
#include <stdint.h>
#include <thread>
@@ -1572,4 +1573,127 @@ BOOST_AUTO_TEST_CASE(test_Capitalize)
BOOST_CHECK_EQUAL(Capitalize("\x00\xfe\xff"), "\x00\xfe\xff");
}
+static std::string SpanToStr(Span<const char>& span)
+{
+ return std::string(span.begin(), span.end());
+}
+
+BOOST_AUTO_TEST_CASE(test_spanparsing)
+{
+ using namespace spanparsing;
+ std::string input;
+ Span<const char> sp;
+ bool success;
+
+ // Const(...): parse a constant, update span to skip it if successful
+ input = "MilkToastHoney";
+ sp = MakeSpan(input);
+ success = Const("", sp); // empty
+ BOOST_CHECK(success);
+ BOOST_CHECK_EQUAL(SpanToStr(sp), "MilkToastHoney");
+
+ success = Const("Milk", sp);
+ BOOST_CHECK(success);
+ BOOST_CHECK_EQUAL(SpanToStr(sp), "ToastHoney");
+
+ success = Const("Bread", sp);
+ BOOST_CHECK(!success);
+
+ success = Const("Toast", sp);
+ BOOST_CHECK(success);
+ BOOST_CHECK_EQUAL(SpanToStr(sp), "Honey");
+
+ success = Const("Honeybadger", sp);
+ BOOST_CHECK(!success);
+
+ success = Const("Honey", sp);
+ BOOST_CHECK(success);
+ BOOST_CHECK_EQUAL(SpanToStr(sp), "");
+
+ // Func(...): parse a function call, update span to argument if successful
+ input = "Foo(Bar(xy,z()))";
+ sp = MakeSpan(input);
+
+ success = Func("FooBar", sp);
+ BOOST_CHECK(!success);
+
+ success = Func("Foo(", sp);
+ BOOST_CHECK(!success);
+
+ success = Func("Foo", sp);
+ BOOST_CHECK(success);
+ BOOST_CHECK_EQUAL(SpanToStr(sp), "Bar(xy,z())");
+
+ success = Func("Bar", sp);
+ BOOST_CHECK(success);
+ BOOST_CHECK_EQUAL(SpanToStr(sp), "xy,z()");
+
+ success = Func("xy", sp);
+ BOOST_CHECK(!success);
+
+ // Expr(...): return expression that span begins with, update span to skip it
+ Span<const char> result;
+
+ input = "(n*(n-1))/2";
+ sp = MakeSpan(input);
+ result = Expr(sp);
+ BOOST_CHECK_EQUAL(SpanToStr(result), "(n*(n-1))/2");
+ BOOST_CHECK_EQUAL(SpanToStr(sp), "");
+
+ input = "foo,bar";
+ sp = MakeSpan(input);
+ result = Expr(sp);
+ BOOST_CHECK_EQUAL(SpanToStr(result), "foo");
+ BOOST_CHECK_EQUAL(SpanToStr(sp), ",bar");
+
+ input = "(aaaaa,bbbbb()),c";
+ sp = MakeSpan(input);
+ result = Expr(sp);
+ BOOST_CHECK_EQUAL(SpanToStr(result), "(aaaaa,bbbbb())");
+ BOOST_CHECK_EQUAL(SpanToStr(sp), ",c");
+
+ input = "xyz)foo";
+ sp = MakeSpan(input);
+ result = Expr(sp);
+ BOOST_CHECK_EQUAL(SpanToStr(result), "xyz");
+ BOOST_CHECK_EQUAL(SpanToStr(sp), ")foo");
+
+ input = "((a),(b),(c)),xxx";
+ sp = MakeSpan(input);
+ result = Expr(sp);
+ BOOST_CHECK_EQUAL(SpanToStr(result), "((a),(b),(c))");
+ BOOST_CHECK_EQUAL(SpanToStr(sp), ",xxx");
+
+ // Split(...): split a string on every instance of sep, return vector
+ std::vector<Span<const char>> results;
+
+ input = "xxx";
+ results = Split(MakeSpan(input), 'x');
+ BOOST_CHECK_EQUAL(results.size(), 4);
+ BOOST_CHECK_EQUAL(SpanToStr(results[0]), "");
+ BOOST_CHECK_EQUAL(SpanToStr(results[1]), "");
+ BOOST_CHECK_EQUAL(SpanToStr(results[2]), "");
+ BOOST_CHECK_EQUAL(SpanToStr(results[3]), "");
+
+ input = "one#two#three";
+ results = Split(MakeSpan(input), '-');
+ BOOST_CHECK_EQUAL(results.size(), 1);
+ BOOST_CHECK_EQUAL(SpanToStr(results[0]), "one#two#three");
+
+ input = "one#two#three";
+ results = Split(MakeSpan(input), '#');
+ BOOST_CHECK_EQUAL(results.size(), 3);
+ BOOST_CHECK_EQUAL(SpanToStr(results[0]), "one");
+ BOOST_CHECK_EQUAL(SpanToStr(results[1]), "two");
+ BOOST_CHECK_EQUAL(SpanToStr(results[2]), "three");
+
+ input = "*foo*bar*";
+ results = Split(MakeSpan(input), '*');
+ BOOST_CHECK_EQUAL(results.size(), 4);
+ BOOST_CHECK_EQUAL(SpanToStr(results[0]), "");
+ BOOST_CHECK_EQUAL(SpanToStr(results[1]), "foo");
+ BOOST_CHECK_EQUAL(SpanToStr(results[2]), "bar");
+ BOOST_CHECK_EQUAL(SpanToStr(results[3]), "");
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/util/spanparsing.cpp b/src/util/spanparsing.cpp
new file mode 100644
index 0000000000..0c8575399a
--- /dev/null
+++ b/src/util/spanparsing.cpp
@@ -0,0 +1,67 @@
+// Copyright (c) 2018 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 <util/spanparsing.h>
+
+#include <span.h>
+
+#include <string>
+#include <vector>
+
+namespace spanparsing {
+
+bool Const(const std::string& str, Span<const char>& sp)
+{
+ if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) {
+ sp = sp.subspan(str.size());
+ return true;
+ }
+ return false;
+}
+
+bool Func(const std::string& str, Span<const char>& sp)
+{
+ if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) {
+ sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2);
+ return true;
+ }
+ return false;
+}
+
+Span<const char> Expr(Span<const char>& sp)
+{
+ int level = 0;
+ auto it = sp.begin();
+ while (it != sp.end()) {
+ if (*it == '(') {
+ ++level;
+ } else if (level && *it == ')') {
+ --level;
+ } else if (level == 0 && (*it == ')' || *it == ',')) {
+ break;
+ }
+ ++it;
+ }
+ Span<const char> ret = sp.first(it - sp.begin());
+ sp = sp.subspan(it - sp.begin());
+ return ret;
+}
+
+std::vector<Span<const char>> Split(const Span<const char>& sp, char sep)
+{
+ std::vector<Span<const char>> ret;
+ auto it = sp.begin();
+ auto start = it;
+ while (it != sp.end()) {
+ if (*it == sep) {
+ ret.emplace_back(start, it);
+ start = it + 1;
+ }
+ ++it;
+ }
+ ret.emplace_back(start, it);
+ return ret;
+}
+
+} // namespace spanparsing
diff --git a/src/util/spanparsing.h b/src/util/spanparsing.h
new file mode 100644
index 0000000000..63f54758bd
--- /dev/null
+++ b/src/util/spanparsing.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2018 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_UTIL_SPANPARSING_H
+#define BITCOIN_UTIL_SPANPARSING_H
+
+#include <span.h>
+
+#include <string>
+#include <vector>
+
+namespace spanparsing {
+
+/** Parse a constant.
+ *
+ * If sp's initial part matches str, sp is updated to skip that part, and true is returned.
+ * Otherwise sp is unmodified and false is returned.
+ */
+bool Const(const std::string& str, Span<const char>& sp);
+
+/** Parse a function call.
+ *
+ * If sp's initial part matches str + "(", and sp ends with ")", sp is updated to be the
+ * section between the braces, and true is returned. Otherwise sp is unmodified and false
+ * is returned.
+ */
+bool Func(const std::string& str, Span<const char>& sp);
+
+/** Extract the expression that sp begins with.
+ *
+ * This function will return the initial part of sp, up to (but not including) the first
+ * comma or closing brace, skipping ones that are surrounded by braces. So for example,
+ * for "foo(bar(1),2),3" the initial part "foo(bar(1),2)" will be returned. sp will be
+ * updated to skip the initial part that is returned.
+ */
+Span<const char> Expr(Span<const char>& sp);
+
+/** Split a string on every instance of sep, returning a vector.
+ *
+ * If sep does not occur in sp, a singleton with the entirety of sp is returned.
+ *
+ * Note that this function does not care about braces, so splitting
+ * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
+ */
+std::vector<Span<const char>> Split(const Span<const char>& sp, char sep);
+
+} // namespace spanparsing
+
+#endif // BITCOIN_UTIL_SPANPARSING_H
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index fc3be2b6ab..73523ca36d 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -338,6 +338,84 @@ BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
BOOST_CHECK_EQUAL(values[1], "val_rr1");
}
+// Test some watch-only wallet methods by the procedure of loading (LoadWatchOnly),
+// checking (HaveWatchOnly), getting (GetWatchPubKey) and removing (RemoveWatchOnly) a
+// given PubKey, resp. its corresponding P2PK Script. Results of the the impact on
+// the address -> PubKey map is dependent on whether the PubKey is a point on the curve
+static void TestWatchOnlyPubKey(CWallet& wallet, const CPubKey& add_pubkey)
+{
+ CScript p2pk = GetScriptForRawPubKey(add_pubkey);
+ CKeyID add_address = add_pubkey.GetID();
+ CPubKey found_pubkey;
+ LOCK(wallet.cs_wallet);
+
+ // all Scripts (i.e. also all PubKeys) are added to the general watch-only set
+ BOOST_CHECK(!wallet.HaveWatchOnly(p2pk));
+ wallet.LoadWatchOnly(p2pk);
+ BOOST_CHECK(wallet.HaveWatchOnly(p2pk));
+
+ // only PubKeys on the curve shall be added to the watch-only address -> PubKey map
+ bool is_pubkey_fully_valid = add_pubkey.IsFullyValid();
+ if (is_pubkey_fully_valid) {
+ BOOST_CHECK(wallet.GetWatchPubKey(add_address, found_pubkey));
+ BOOST_CHECK(found_pubkey == add_pubkey);
+ } else {
+ BOOST_CHECK(!wallet.GetWatchPubKey(add_address, found_pubkey));
+ BOOST_CHECK(found_pubkey == CPubKey()); // passed key is unchanged
+ }
+
+ wallet.RemoveWatchOnly(p2pk);
+ BOOST_CHECK(!wallet.HaveWatchOnly(p2pk));
+
+ if (is_pubkey_fully_valid) {
+ BOOST_CHECK(!wallet.GetWatchPubKey(add_address, found_pubkey));
+ BOOST_CHECK(found_pubkey == add_pubkey); // passed key is unchanged
+ }
+}
+
+// Cryptographically invalidate a PubKey whilst keeping length and first byte
+static void PollutePubKey(CPubKey& pubkey)
+{
+ std::vector<unsigned char> pubkey_raw(pubkey.begin(), pubkey.end());
+ std::fill(pubkey_raw.begin()+1, pubkey_raw.end(), 0);
+ pubkey = CPubKey(pubkey_raw);
+ assert(!pubkey.IsFullyValid());
+ assert(pubkey.IsValid());
+}
+
+// Test watch-only wallet logic for PubKeys
+BOOST_AUTO_TEST_CASE(WatchOnlyPubKeys)
+{
+ CKey key;
+ CPubKey pubkey;
+
+ BOOST_CHECK(!m_wallet.HaveWatchOnly());
+
+ // uncompressed valid PubKey
+ key.MakeNewKey(false);
+ pubkey = key.GetPubKey();
+ assert(!pubkey.IsCompressed());
+ TestWatchOnlyPubKey(m_wallet, pubkey);
+
+ // uncompressed cryptographically invalid PubKey
+ PollutePubKey(pubkey);
+ TestWatchOnlyPubKey(m_wallet, pubkey);
+
+ // compressed valid PubKey
+ key.MakeNewKey(true);
+ pubkey = key.GetPubKey();
+ assert(pubkey.IsCompressed());
+ TestWatchOnlyPubKey(m_wallet, pubkey);
+
+ // compressed cryptographically invalid PubKey
+ PollutePubKey(pubkey);
+ TestWatchOnlyPubKey(m_wallet, pubkey);
+
+ // invalid empty PubKey
+ pubkey = CPubKey();
+ TestWatchOnlyPubKey(m_wallet, pubkey);
+}
+
class ListCoinsTestingSetup : public TestChain100Setup
{
public: