Skip to content

Commit f4073c5

Browse files
committed
Merge bitcoin#28578: fuzz: add target for DescriptorScriptPubKeyMan
47e5c99 fuzz: add target for `DescriptorScriptPubKeyMan` (brunoerg) 641dddf fuzz: create ConsumeCoins (brunoerg) 2e1833c fuzz: move `MockedDescriptorConverter` to `fuzz/util` (brunoerg) Pull request description: This PR adds fuzz target for `DescriptorScriptPubKeyMan`. Also, moves `MockedDescriptorConverter` to `fuzz/util/descriptor` to be used here and in `descriptor` target. ACKs for top commit: maflcko: lgtm ACK 47e5c99 🏓 dergoegge: ACK 47e5c99 Tree-SHA512: 519acca6d7b7a3a0bfc031441b02d5980b12bfb97198bd1958a83cd815ceb9eb1499a48a3f0a7fe20e5d06d83b89335d987376fc0a014e2106b0bc0e9838dd02
2 parents ddc4b98 + 47e5c99 commit f4073c5

File tree

9 files changed

+311
-108
lines changed

9 files changed

+311
-108
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ FUZZ_WALLET_SRC = \
205205

206206
if USE_SQLITE
207207
FUZZ_WALLET_SRC += \
208-
wallet/test/fuzz/notifications.cpp
208+
wallet/test/fuzz/notifications.cpp \
209+
wallet/test/fuzz/scriptpubkeyman.cpp
209210
endif # USE_SQLITE
210211

211212
BITCOIN_TEST_SUITE += \

src/Makefile.test_fuzz.include

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ TEST_FUZZ_H = \
1111
test/fuzz/fuzz.h \
1212
test/fuzz/FuzzedDataProvider.h \
1313
test/fuzz/util.h \
14+
test/fuzz/util/descriptor.h \
1415
test/fuzz/util/mempool.h \
1516
test/fuzz/util/net.h
1617

@@ -19,6 +20,7 @@ libtest_fuzz_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
1920
libtest_fuzz_a_SOURCES = \
2021
test/fuzz/fuzz.cpp \
2122
test/fuzz/util.cpp \
23+
test/fuzz/util/descriptor.cpp \
2224
test/fuzz/util/mempool.cpp \
2325
test/fuzz/util/net.cpp \
2426
$(TEST_FUZZ_H)

src/test/fuzz/descriptor_parse.cpp

Lines changed: 1 addition & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -7,104 +7,10 @@
77
#include <pubkey.h>
88
#include <script/descriptor.h>
99
#include <test/fuzz/fuzz.h>
10+
#include <test/fuzz/util/descriptor.h>
1011
#include <util/chaintype.h>
1112
#include <util/strencodings.h>
1213

13-
//! Types are raw (un)compressed pubkeys, raw xonly pubkeys, raw privkeys (WIF), xpubs, xprvs.
14-
static constexpr uint8_t KEY_TYPES_COUNT{6};
15-
//! How many keys we'll generate in total.
16-
static constexpr size_t TOTAL_KEYS_GENERATED{std::numeric_limits<uint8_t>::max() + 1};
17-
18-
/**
19-
* Converts a mocked descriptor string to a valid one. Every key in a mocked descriptor key is
20-
* represented by 2 hex characters preceded by the '%' character. We parse the two hex characters
21-
* as an index in a list of pre-generated keys. This list contains keys of the various types
22-
* accepted in descriptor keys expressions.
23-
*/
24-
class MockedDescriptorConverter {
25-
//! 256 keys of various types.
26-
std::array<std::string, TOTAL_KEYS_GENERATED> keys_str;
27-
28-
public:
29-
// We derive the type of key to generate from the 1-byte id parsed from hex.
30-
bool IdIsCompPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 0; }
31-
bool IdIsUnCompPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 1; }
32-
bool IdIsXOnlyPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 2; }
33-
bool IdIsConstPrivKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 3; }
34-
bool IdIsXpub(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 4; }
35-
bool IdIsXprv(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 5; }
36-
37-
//! When initializing the target, populate the list of keys.
38-
void Init() {
39-
// The data to use as a private key or a seed for an xprv.
40-
std::array<std::byte, 32> key_data{std::byte{1}};
41-
// Generate keys of all kinds and store them in the keys array.
42-
for (size_t i{0}; i < TOTAL_KEYS_GENERATED; i++) {
43-
key_data[31] = std::byte(i);
44-
45-
// If this is a "raw" key, generate a normal privkey. Otherwise generate
46-
// an extended one.
47-
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i) || IdIsXOnlyPubKey(i) || IdIsConstPrivKey(i)) {
48-
CKey privkey;
49-
privkey.Set(UCharCast(key_data.begin()), UCharCast(key_data.end()), !IdIsUnCompPubKey(i));
50-
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i)) {
51-
CPubKey pubkey{privkey.GetPubKey()};
52-
keys_str[i] = HexStr(pubkey);
53-
} else if (IdIsXOnlyPubKey(i)) {
54-
const XOnlyPubKey pubkey{privkey.GetPubKey()};
55-
keys_str[i] = HexStr(pubkey);
56-
} else {
57-
keys_str[i] = EncodeSecret(privkey);
58-
}
59-
} else {
60-
CExtKey ext_privkey;
61-
ext_privkey.SetSeed(key_data);
62-
if (IdIsXprv(i)) {
63-
keys_str[i] = EncodeExtKey(ext_privkey);
64-
} else {
65-
const CExtPubKey ext_pubkey{ext_privkey.Neuter()};
66-
keys_str[i] = EncodeExtPubKey(ext_pubkey);
67-
}
68-
}
69-
}
70-
}
71-
72-
//! Parse an id in the keys vectors from a 2-characters hex string.
73-
std::optional<uint8_t> IdxFromHex(std::string_view hex_characters) const {
74-
if (hex_characters.size() != 2) return {};
75-
auto idx = ParseHex(hex_characters);
76-
if (idx.size() != 1) return {};
77-
return idx[0];
78-
}
79-
80-
//! Get an actual descriptor string from a descriptor string whose keys were mocked.
81-
std::optional<std::string> GetDescriptor(std::string_view mocked_desc) const {
82-
// The smallest fragment would be "pk(%00)"
83-
if (mocked_desc.size() < 7) return {};
84-
85-
// The actual descriptor string to be returned.
86-
std::string desc;
87-
desc.reserve(mocked_desc.size());
88-
89-
// Replace all occurrences of '%' followed by two hex characters with the corresponding key.
90-
for (size_t i = 0; i < mocked_desc.size();) {
91-
if (mocked_desc[i] == '%') {
92-
if (i + 3 >= mocked_desc.size()) return {};
93-
if (const auto idx = IdxFromHex(mocked_desc.substr(i + 1, 2))) {
94-
desc += keys_str[*idx];
95-
i += 3;
96-
} else {
97-
return {};
98-
}
99-
} else {
100-
desc += mocked_desc[i++];
101-
}
102-
}
103-
104-
return desc;
105-
}
106-
};
107-
10814
//! The converter of mocked descriptors, needs to be initialized when the target is.
10915
MockedDescriptorConverter MOCKED_DESC_CONVERTER;
11016

src/test/fuzz/script_sign.cpp

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,7 @@ FUZZ_TARGET(script_sign, .init = initialize_script_sign)
125125
}
126126
(void)signature_creator.CreateSig(provider, vch_sig, address, ConsumeScript(fuzzed_data_provider), fuzzed_data_provider.PickValueInArray({SigVersion::BASE, SigVersion::WITNESS_V0}));
127127
}
128-
std::map<COutPoint, Coin> coins;
129-
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
130-
const std::optional<COutPoint> outpoint = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
131-
if (!outpoint) {
132-
break;
133-
}
134-
const std::optional<Coin> coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
135-
if (!coin) {
136-
break;
137-
}
138-
coins[*outpoint] = *coin;
139-
}
128+
std::map<COutPoint, Coin> coins{ConsumeCoins(fuzzed_data_provider)};
140129
std::map<int, bilingual_str> input_errors;
141130
(void)SignTransaction(sign_transaction_tx_to, &provider, coins, fuzzed_data_provider.ConsumeIntegral<int>(), input_errors);
142131
}

src/test/fuzz/util.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,24 @@ uint32_t ConsumeSequence(FuzzedDataProvider& fuzzed_data_provider) noexcept
164164
fuzzed_data_provider.ConsumeIntegral<uint32_t>();
165165
}
166166

167+
std::map<COutPoint, Coin> ConsumeCoins(FuzzedDataProvider& fuzzed_data_provider) noexcept
168+
{
169+
std::map<COutPoint, Coin> coins;
170+
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
171+
const std::optional<COutPoint> outpoint{ConsumeDeserializable<COutPoint>(fuzzed_data_provider)};
172+
if (!outpoint) {
173+
break;
174+
}
175+
const std::optional<Coin> coin{ConsumeDeserializable<Coin>(fuzzed_data_provider)};
176+
if (!coin) {
177+
break;
178+
}
179+
coins[*outpoint] = *coin;
180+
}
181+
182+
return coins;
183+
}
184+
167185
CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept
168186
{
169187
CTxDestination tx_destination;

src/test/fuzz/util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ template <typename WeakEnumType, size_t size>
181181
return UintToArith256(ConsumeUInt256(fuzzed_data_provider));
182182
}
183183

184+
[[nodiscard]] std::map<COutPoint, Coin> ConsumeCoins(FuzzedDataProvider& fuzzed_data_provider) noexcept;
185+
184186
[[nodiscard]] CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept;
185187

186188
[[nodiscard]] CKey ConsumePrivateKey(FuzzedDataProvider& fuzzed_data_provider, std::optional<bool> compressed = std::nullopt) noexcept;

src/test/fuzz/util/descriptor.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) 2023-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <test/fuzz/util/descriptor.h>
6+
7+
void MockedDescriptorConverter::Init() {
8+
// The data to use as a private key or a seed for an xprv.
9+
std::array<std::byte, 32> key_data{std::byte{1}};
10+
// Generate keys of all kinds and store them in the keys array.
11+
for (size_t i{0}; i < TOTAL_KEYS_GENERATED; i++) {
12+
key_data[31] = std::byte(i);
13+
14+
// If this is a "raw" key, generate a normal privkey. Otherwise generate
15+
// an extended one.
16+
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i) || IdIsXOnlyPubKey(i) || IdIsConstPrivKey(i)) {
17+
CKey privkey;
18+
privkey.Set(UCharCast(key_data.begin()), UCharCast(key_data.end()), !IdIsUnCompPubKey(i));
19+
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i)) {
20+
CPubKey pubkey{privkey.GetPubKey()};
21+
keys_str[i] = HexStr(pubkey);
22+
} else if (IdIsXOnlyPubKey(i)) {
23+
const XOnlyPubKey pubkey{privkey.GetPubKey()};
24+
keys_str[i] = HexStr(pubkey);
25+
} else {
26+
keys_str[i] = EncodeSecret(privkey);
27+
}
28+
} else {
29+
CExtKey ext_privkey;
30+
ext_privkey.SetSeed(key_data);
31+
if (IdIsXprv(i)) {
32+
keys_str[i] = EncodeExtKey(ext_privkey);
33+
} else {
34+
const CExtPubKey ext_pubkey{ext_privkey.Neuter()};
35+
keys_str[i] = EncodeExtPubKey(ext_pubkey);
36+
}
37+
}
38+
}
39+
}
40+
41+
std::optional<uint8_t> MockedDescriptorConverter::IdxFromHex(std::string_view hex_characters) const {
42+
if (hex_characters.size() != 2) return {};
43+
auto idx = ParseHex(hex_characters);
44+
if (idx.size() != 1) return {};
45+
return idx[0];
46+
}
47+
48+
std::optional<std::string> MockedDescriptorConverter::GetDescriptor(std::string_view mocked_desc) const {
49+
// The smallest fragment would be "pk(%00)"
50+
if (mocked_desc.size() < 7) return {};
51+
52+
// The actual descriptor string to be returned.
53+
std::string desc;
54+
desc.reserve(mocked_desc.size());
55+
56+
// Replace all occurrences of '%' followed by two hex characters with the corresponding key.
57+
for (size_t i = 0; i < mocked_desc.size();) {
58+
if (mocked_desc[i] == '%') {
59+
if (i + 3 >= mocked_desc.size()) return {};
60+
if (const auto idx = IdxFromHex(mocked_desc.substr(i + 1, 2))) {
61+
desc += keys_str[*idx];
62+
i += 3;
63+
} else {
64+
return {};
65+
}
66+
} else {
67+
desc += mocked_desc[i++];
68+
}
69+
}
70+
71+
return desc;
72+
}

src/test/fuzz/util/descriptor.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2023-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_TEST_FUZZ_UTIL_DESCRIPTOR_H
6+
#define BITCOIN_TEST_FUZZ_UTIL_DESCRIPTOR_H
7+
8+
#include <key_io.h>
9+
#include <util/strencodings.h>
10+
#include <script/descriptor.h>
11+
12+
#include <functional>
13+
14+
/**
15+
* Converts a mocked descriptor string to a valid one. Every key in a mocked descriptor key is
16+
* represented by 2 hex characters preceded by the '%' character. We parse the two hex characters
17+
* as an index in a list of pre-generated keys. This list contains keys of the various types
18+
* accepted in descriptor keys expressions.
19+
*/
20+
class MockedDescriptorConverter {
21+
private:
22+
//! Types are raw (un)compressed pubkeys, raw xonly pubkeys, raw privkeys (WIF), xpubs, xprvs.
23+
static constexpr uint8_t KEY_TYPES_COUNT{6};
24+
//! How many keys we'll generate in total.
25+
static constexpr size_t TOTAL_KEYS_GENERATED{std::numeric_limits<uint8_t>::max() + 1};
26+
//! 256 keys of various types.
27+
std::array<std::string, TOTAL_KEYS_GENERATED> keys_str;
28+
29+
public:
30+
// We derive the type of key to generate from the 1-byte id parsed from hex.
31+
bool IdIsCompPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 0; }
32+
bool IdIsUnCompPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 1; }
33+
bool IdIsXOnlyPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 2; }
34+
bool IdIsConstPrivKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 3; }
35+
bool IdIsXpub(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 4; }
36+
bool IdIsXprv(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 5; }
37+
38+
//! When initializing the target, populate the list of keys.
39+
void Init();
40+
41+
//! Parse an id in the keys vectors from a 2-characters hex string.
42+
std::optional<uint8_t> IdxFromHex(std::string_view hex_characters) const;
43+
44+
//! Get an actual descriptor string from a descriptor string whose keys were mocked.
45+
std::optional<std::string> GetDescriptor(std::string_view mocked_desc) const;
46+
};
47+
48+
#endif // BITCOIN_TEST_FUZZ_UTIL_DESCRIPTOR_H

0 commit comments

Comments
 (0)