Skip to content

Commit 930e2f2

Browse files
committed
Update the Miniscript fuzz tests from Bitcoin Core master
1 parent f62abcf commit 930e2f2

File tree

1 file changed

+45
-25
lines changed

1 file changed

+45
-25
lines changed

bitcoin/test/fuzz/miniscript.cpp

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2021 The Bitcoin Core developers
1+
// Copyright (c) 2021-2022 The Bitcoin Core developers
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

@@ -14,7 +14,7 @@
1414

1515
namespace {
1616

17-
//! Some pre-computed data to simulate challenges.
17+
//! Some pre-computed data for more efficient string roundtrips and to simulate challenges.
1818
struct TestData {
1919
typedef CPubKey Key;
2020

@@ -72,10 +72,18 @@ struct TestData {
7272
}
7373
} TEST_DATA;
7474

75-
//! Context to parse a Miniscript node to and from Script or text representation.
75+
/**
76+
* Context to parse a Miniscript node to and from Script or text representation.
77+
* Uses an integer (an index in the dummy keys array from the test data) as keys in order
78+
* to focus on fuzzing the Miniscript nodes' test representation, not the key representation.
79+
*/
7680
struct ParserContext {
7781
typedef CPubKey Key;
7882

83+
bool KeyCompare(const Key& a, const Key& b) const {
84+
return a < b;
85+
}
86+
7987
std::optional<std::string> ToString(const Key& key) const
8088
{
8189
auto it = TEST_DATA.dummy_key_idx_map.find(key);
@@ -84,12 +92,12 @@ struct ParserContext {
8492
return HexStr(Span{&idx, 1});
8593
}
8694

87-
const std::vector<unsigned char> ToPKBytes(const Key& key) const
95+
std::vector<unsigned char> ToPKBytes(const Key& key) const
8896
{
8997
return {key.begin(), key.end()};
9098
}
9199

92-
const std::vector<unsigned char> ToPKHBytes(const Key& key) const
100+
std::vector<unsigned char> ToPKHBytes(const Key& key) const
93101
{
94102
const auto h = Hash160(key);
95103
return {h.begin(), h.end()};
@@ -104,15 +112,15 @@ struct ParserContext {
104112
}
105113

106114
template<typename I>
107-
std::optional<CPubKey> FromPKBytes(I first, I last) const {
115+
std::optional<Key> FromPKBytes(I first, I last) const {
108116
CPubKey key;
109117
key.Set(first, last);
110118
if (!key.IsValid()) return {};
111119
return key;
112120
}
113121

114122
template<typename I>
115-
std::optional<CPubKey> FromPKHBytes(I first, I last) const {
123+
std::optional<Key> FromPKHBytes(I first, I last) const {
116124
assert(last - first == 20);
117125
CKeyID keyid;
118126
std::copy(first, last, keyid.begin());
@@ -124,20 +132,23 @@ struct ParserContext {
124132

125133
//! Context that implements naive conversion from/to script only, for roundtrip testing.
126134
struct ScriptParserContext {
135+
//! For Script roundtrip we never need the key from a key hash.
127136
struct Key {
128137
bool is_hash;
129138
std::vector<unsigned char> data;
130-
131-
bool operator<(Key k) const { return data < k.data; }
132139
};
133140

141+
bool KeyCompare(const Key& a, const Key& b) const {
142+
return a.data < b.data;
143+
}
144+
134145
const std::vector<unsigned char>& ToPKBytes(const Key& key) const
135146
{
136147
assert(!key.is_hash);
137148
return key.data;
138149
}
139150

140-
const std::vector<unsigned char> ToPKHBytes(const Key& key) const
151+
std::vector<unsigned char> ToPKHBytes(const Key& key) const
141152
{
142153
if (key.is_hash) return key.data;
143154
const auto h = Hash160(key.data);
@@ -223,17 +234,26 @@ struct CheckerContext: BaseSignatureChecker {
223234
bool CheckSequence(const CScriptNum& nSequence) const override { return nSequence.GetInt64() & 1; }
224235
} CHECKER_CTX;
225236

237+
//! Context to check for duplicates when instancing a Node.
238+
struct KeyComparator {
239+
bool KeyCompare(const CPubKey& a, const CPubKey& b) const {
240+
return a < b;
241+
}
242+
} KEY_COMP;
243+
226244
// A dummy scriptsig to pass to VerifyScript (we always use Segwit v0).
227245
const CScript DUMMY_SCRIPTSIG;
228246

229247
using Fragment = miniscript::Fragment;
230248
using NodeRef = miniscript::NodeRef<CPubKey>;
231249
using Node = miniscript::Node<CPubKey>;
232250
using Type = miniscript::Type;
251+
// https://github.com/llvm/llvm-project/issues/53444
252+
// NOLINTNEXTLINE(misc-unused-using-decls)
233253
using miniscript::operator"" _mst;
234254

235255
//! Construct a miniscript node as a shared_ptr.
236-
template<typename... Args> NodeRef MakeNodeRef(Args&&... args) { return miniscript::MakeNodeRef<CPubKey>(std::forward<Args>(args)...); }
256+
template<typename... Args> NodeRef MakeNodeRef(Args&&... args) { return miniscript::MakeNodeRef<CPubKey>(KEY_COMP, std::forward<Args>(args)...); }
237257

238258
/** Information about a yet to be constructed Miniscript node. */
239259
struct NodeInfo {
@@ -577,7 +597,7 @@ struct SmartInfo
577597
// Remove all recipes which involve non-constructible types.
578598
type_it->second.erase(std::remove_if(type_it->second.begin(), type_it->second.end(),
579599
[&](const recipe& rec) {
580-
return !std::all_of(rec.second.begin(), rec.second.end(), known_constructible);
600+
return !std::all_of(rec.second.begin(), rec.second.end(), known_constructible);
581601
}), type_it->second.end());
582602
// Delete types entirely which have no recipes left.
583603
if (type_it->second.empty()) {
@@ -700,7 +720,7 @@ NodeRef GenNode(F ConsumeNode, Type root_type = ""_mst, bool strict_valid = fals
700720
// Fragment/children have not been decided yet. Decide them.
701721
auto node_info = ConsumeNode(type_needed);
702722
if (!node_info) return {};
703-
auto subtypes = std::move(node_info)->subtypes;
723+
auto subtypes = node_info->subtypes;
704724
todo.back().second = std::move(node_info);
705725
todo.reserve(todo.size() + subtypes.size());
706726
// As elements on the todo stack are processed back to front, construct
@@ -713,7 +733,7 @@ NodeRef GenNode(F ConsumeNode, Type root_type = ""_mst, bool strict_valid = fals
713733
// those children have been constructed at the back of stack. Pop
714734
// that entry off todo, and use it to construct a new NodeRef on
715735
// stack.
716-
const NodeInfo& info = *todo.back().second;
736+
NodeInfo& info = *todo.back().second;
717737
// Gather children from the back of stack.
718738
std::vector<NodeRef> sub;
719739
sub.reserve(info.n_subs);
@@ -751,9 +771,9 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
751771
if (!node) return;
752772

753773
// Check that it roundtrips to text representation
754-
std::string str;
755-
assert(node->ToString(PARSER_CTX, str));
756-
auto parsed = miniscript::FromString(str, PARSER_CTX);
774+
std::optional<std::string> str{node->ToString(PARSER_CTX)};
775+
assert(str);
776+
auto parsed = miniscript::FromString(*str, PARSER_CTX);
757777
assert(parsed);
758778
assert(*parsed == *node);
759779

@@ -780,7 +800,7 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
780800
assert(decoded->ToScript(PARSER_CTX) == script);
781801
assert(decoded->GetType() == node->GetType());
782802

783-
if (provider.ConsumeBool()) {
803+
if (provider.ConsumeBool() && node->GetOps() < MAX_OPS_PER_SCRIPT && node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
784804
// Optionally pad the script with OP_NOPs to max op the ops limit of the constructed script.
785805
// This makes the script obviously not actually miniscript-compatible anymore, but the
786806
// signatures constructed in this test don't commit to the script anyway, so the same
@@ -835,13 +855,13 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
835855
assert(res || serror == ScriptError::SCRIPT_ERR_OP_COUNT || serror == ScriptError::SCRIPT_ERR_STACK_SIZE);
836856
}
837857

838-
if (node->IsSaneTopLevel()) {
858+
if (node->IsSane()) {
839859
// For sane nodes, the two algorithms behave identically.
840860
assert(mal_success == nonmal_success);
841861
}
842862

843863
// Verify that if a node is policy-satisfiable, the malleable satisfaction
844-
// algorithm succeeds. Given that under IsSaneTopLevel() both satisfactions
864+
// algorithm succeeds. Given that under IsSane() both satisfactions
845865
// are identical, this implies that for such nodes, the non-malleable
846866
// satisfaction will also match the expected policy.
847867
bool satisfiable = node->IsSatisfiable([](const Node& node) -> bool {
@@ -880,6 +900,8 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
880900
assert(mal_success == satisfiable);
881901
}
882902

903+
} // namespace
904+
883905
void FuzzInit()
884906
{
885907
ECC_Start();
@@ -892,8 +914,6 @@ void FuzzInitSmart()
892914
SMARTINFO.Init();
893915
}
894916

895-
} // namespace
896-
897917
/** Fuzz target that runs TestNode on nodes generated using ConsumeNodeStable. */
898918
FUZZ_TARGET_INIT(miniscript_stable, FuzzInit)
899919
{
@@ -923,9 +943,9 @@ FUZZ_TARGET_INIT(miniscript_string, FuzzInit)
923943
auto parsed = miniscript::FromString(str, PARSER_CTX);
924944
if (!parsed) return;
925945

926-
std::string str2;
927-
assert(parsed->ToString(PARSER_CTX, str2));
928-
auto parsed2 = miniscript::FromString(str2, PARSER_CTX);
946+
const auto str2 = parsed->ToString(PARSER_CTX);
947+
assert(str2);
948+
auto parsed2 = miniscript::FromString(*str2, PARSER_CTX);
929949
assert(parsed2);
930950
assert(*parsed == *parsed2);
931951
}

0 commit comments

Comments
 (0)