mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-01 08:44:02 +02:00
Merge bitcoin/bitcoin#28578: fuzz: add target for DescriptorScriptPubKeyMan
47e5c9994cfuzz: add target for `DescriptorScriptPubKeyMan` (brunoerg)641dddf018fuzz: create ConsumeCoins (brunoerg)2e1833ca13fuzz: 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 ACK47e5c9994c🏓 dergoegge: ACK47e5c9994cTree-SHA512: 519acca6d7b7a3a0bfc031441b02d5980b12bfb97198bd1958a83cd815ceb9eb1499a48a3f0a7fe20e5d06d83b89335d987376fc0a014e2106b0bc0e9838dd02
This commit is contained in:
@@ -7,104 +7,10 @@
|
||||
#include <pubkey.h>
|
||||
#include <script/descriptor.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util/descriptor.h>
|
||||
#include <util/chaintype.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
//! Types are raw (un)compressed pubkeys, raw xonly pubkeys, raw privkeys (WIF), xpubs, xprvs.
|
||||
static constexpr uint8_t KEY_TYPES_COUNT{6};
|
||||
//! How many keys we'll generate in total.
|
||||
static constexpr size_t TOTAL_KEYS_GENERATED{std::numeric_limits<uint8_t>::max() + 1};
|
||||
|
||||
/**
|
||||
* Converts a mocked descriptor string to a valid one. Every key in a mocked descriptor key is
|
||||
* represented by 2 hex characters preceded by the '%' character. We parse the two hex characters
|
||||
* as an index in a list of pre-generated keys. This list contains keys of the various types
|
||||
* accepted in descriptor keys expressions.
|
||||
*/
|
||||
class MockedDescriptorConverter {
|
||||
//! 256 keys of various types.
|
||||
std::array<std::string, TOTAL_KEYS_GENERATED> keys_str;
|
||||
|
||||
public:
|
||||
// We derive the type of key to generate from the 1-byte id parsed from hex.
|
||||
bool IdIsCompPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 0; }
|
||||
bool IdIsUnCompPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 1; }
|
||||
bool IdIsXOnlyPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 2; }
|
||||
bool IdIsConstPrivKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 3; }
|
||||
bool IdIsXpub(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 4; }
|
||||
bool IdIsXprv(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 5; }
|
||||
|
||||
//! When initializing the target, populate the list of keys.
|
||||
void Init() {
|
||||
// The data to use as a private key or a seed for an xprv.
|
||||
std::array<std::byte, 32> key_data{std::byte{1}};
|
||||
// Generate keys of all kinds and store them in the keys array.
|
||||
for (size_t i{0}; i < TOTAL_KEYS_GENERATED; i++) {
|
||||
key_data[31] = std::byte(i);
|
||||
|
||||
// If this is a "raw" key, generate a normal privkey. Otherwise generate
|
||||
// an extended one.
|
||||
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i) || IdIsXOnlyPubKey(i) || IdIsConstPrivKey(i)) {
|
||||
CKey privkey;
|
||||
privkey.Set(UCharCast(key_data.begin()), UCharCast(key_data.end()), !IdIsUnCompPubKey(i));
|
||||
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i)) {
|
||||
CPubKey pubkey{privkey.GetPubKey()};
|
||||
keys_str[i] = HexStr(pubkey);
|
||||
} else if (IdIsXOnlyPubKey(i)) {
|
||||
const XOnlyPubKey pubkey{privkey.GetPubKey()};
|
||||
keys_str[i] = HexStr(pubkey);
|
||||
} else {
|
||||
keys_str[i] = EncodeSecret(privkey);
|
||||
}
|
||||
} else {
|
||||
CExtKey ext_privkey;
|
||||
ext_privkey.SetSeed(key_data);
|
||||
if (IdIsXprv(i)) {
|
||||
keys_str[i] = EncodeExtKey(ext_privkey);
|
||||
} else {
|
||||
const CExtPubKey ext_pubkey{ext_privkey.Neuter()};
|
||||
keys_str[i] = EncodeExtPubKey(ext_pubkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Parse an id in the keys vectors from a 2-characters hex string.
|
||||
std::optional<uint8_t> IdxFromHex(std::string_view hex_characters) const {
|
||||
if (hex_characters.size() != 2) return {};
|
||||
auto idx = ParseHex(hex_characters);
|
||||
if (idx.size() != 1) return {};
|
||||
return idx[0];
|
||||
}
|
||||
|
||||
//! Get an actual descriptor string from a descriptor string whose keys were mocked.
|
||||
std::optional<std::string> GetDescriptor(std::string_view mocked_desc) const {
|
||||
// The smallest fragment would be "pk(%00)"
|
||||
if (mocked_desc.size() < 7) return {};
|
||||
|
||||
// The actual descriptor string to be returned.
|
||||
std::string desc;
|
||||
desc.reserve(mocked_desc.size());
|
||||
|
||||
// Replace all occurrences of '%' followed by two hex characters with the corresponding key.
|
||||
for (size_t i = 0; i < mocked_desc.size();) {
|
||||
if (mocked_desc[i] == '%') {
|
||||
if (i + 3 >= mocked_desc.size()) return {};
|
||||
if (const auto idx = IdxFromHex(mocked_desc.substr(i + 1, 2))) {
|
||||
desc += keys_str[*idx];
|
||||
i += 3;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
desc += mocked_desc[i++];
|
||||
}
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
};
|
||||
|
||||
//! The converter of mocked descriptors, needs to be initialized when the target is.
|
||||
MockedDescriptorConverter MOCKED_DESC_CONVERTER;
|
||||
|
||||
|
||||
@@ -125,18 +125,7 @@ FUZZ_TARGET(script_sign, .init = initialize_script_sign)
|
||||
}
|
||||
(void)signature_creator.CreateSig(provider, vch_sig, address, ConsumeScript(fuzzed_data_provider), fuzzed_data_provider.PickValueInArray({SigVersion::BASE, SigVersion::WITNESS_V0}));
|
||||
}
|
||||
std::map<COutPoint, Coin> coins;
|
||||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
|
||||
const std::optional<COutPoint> outpoint = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
|
||||
if (!outpoint) {
|
||||
break;
|
||||
}
|
||||
const std::optional<Coin> coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
|
||||
if (!coin) {
|
||||
break;
|
||||
}
|
||||
coins[*outpoint] = *coin;
|
||||
}
|
||||
std::map<COutPoint, Coin> coins{ConsumeCoins(fuzzed_data_provider)};
|
||||
std::map<int, bilingual_str> input_errors;
|
||||
(void)SignTransaction(sign_transaction_tx_to, &provider, coins, fuzzed_data_provider.ConsumeIntegral<int>(), input_errors);
|
||||
}
|
||||
|
||||
@@ -164,6 +164,24 @@ uint32_t ConsumeSequence(FuzzedDataProvider& fuzzed_data_provider) noexcept
|
||||
fuzzed_data_provider.ConsumeIntegral<uint32_t>();
|
||||
}
|
||||
|
||||
std::map<COutPoint, Coin> ConsumeCoins(FuzzedDataProvider& fuzzed_data_provider) noexcept
|
||||
{
|
||||
std::map<COutPoint, Coin> coins;
|
||||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) {
|
||||
const std::optional<COutPoint> outpoint{ConsumeDeserializable<COutPoint>(fuzzed_data_provider)};
|
||||
if (!outpoint) {
|
||||
break;
|
||||
}
|
||||
const std::optional<Coin> coin{ConsumeDeserializable<Coin>(fuzzed_data_provider)};
|
||||
if (!coin) {
|
||||
break;
|
||||
}
|
||||
coins[*outpoint] = *coin;
|
||||
}
|
||||
|
||||
return coins;
|
||||
}
|
||||
|
||||
CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept
|
||||
{
|
||||
CTxDestination tx_destination;
|
||||
|
||||
@@ -181,6 +181,8 @@ template <typename WeakEnumType, size_t size>
|
||||
return UintToArith256(ConsumeUInt256(fuzzed_data_provider));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::map<COutPoint, Coin> ConsumeCoins(FuzzedDataProvider& fuzzed_data_provider) noexcept;
|
||||
|
||||
[[nodiscard]] CTxDestination ConsumeTxDestination(FuzzedDataProvider& fuzzed_data_provider) noexcept;
|
||||
|
||||
[[nodiscard]] CKey ConsumePrivateKey(FuzzedDataProvider& fuzzed_data_provider, std::optional<bool> compressed = std::nullopt) noexcept;
|
||||
|
||||
72
src/test/fuzz/util/descriptor.cpp
Normal file
72
src/test/fuzz/util/descriptor.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2023-present 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 <test/fuzz/util/descriptor.h>
|
||||
|
||||
void MockedDescriptorConverter::Init() {
|
||||
// The data to use as a private key or a seed for an xprv.
|
||||
std::array<std::byte, 32> key_data{std::byte{1}};
|
||||
// Generate keys of all kinds and store them in the keys array.
|
||||
for (size_t i{0}; i < TOTAL_KEYS_GENERATED; i++) {
|
||||
key_data[31] = std::byte(i);
|
||||
|
||||
// If this is a "raw" key, generate a normal privkey. Otherwise generate
|
||||
// an extended one.
|
||||
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i) || IdIsXOnlyPubKey(i) || IdIsConstPrivKey(i)) {
|
||||
CKey privkey;
|
||||
privkey.Set(UCharCast(key_data.begin()), UCharCast(key_data.end()), !IdIsUnCompPubKey(i));
|
||||
if (IdIsCompPubKey(i) || IdIsUnCompPubKey(i)) {
|
||||
CPubKey pubkey{privkey.GetPubKey()};
|
||||
keys_str[i] = HexStr(pubkey);
|
||||
} else if (IdIsXOnlyPubKey(i)) {
|
||||
const XOnlyPubKey pubkey{privkey.GetPubKey()};
|
||||
keys_str[i] = HexStr(pubkey);
|
||||
} else {
|
||||
keys_str[i] = EncodeSecret(privkey);
|
||||
}
|
||||
} else {
|
||||
CExtKey ext_privkey;
|
||||
ext_privkey.SetSeed(key_data);
|
||||
if (IdIsXprv(i)) {
|
||||
keys_str[i] = EncodeExtKey(ext_privkey);
|
||||
} else {
|
||||
const CExtPubKey ext_pubkey{ext_privkey.Neuter()};
|
||||
keys_str[i] = EncodeExtPubKey(ext_pubkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint8_t> MockedDescriptorConverter::IdxFromHex(std::string_view hex_characters) const {
|
||||
if (hex_characters.size() != 2) return {};
|
||||
auto idx = ParseHex(hex_characters);
|
||||
if (idx.size() != 1) return {};
|
||||
return idx[0];
|
||||
}
|
||||
|
||||
std::optional<std::string> MockedDescriptorConverter::GetDescriptor(std::string_view mocked_desc) const {
|
||||
// The smallest fragment would be "pk(%00)"
|
||||
if (mocked_desc.size() < 7) return {};
|
||||
|
||||
// The actual descriptor string to be returned.
|
||||
std::string desc;
|
||||
desc.reserve(mocked_desc.size());
|
||||
|
||||
// Replace all occurrences of '%' followed by two hex characters with the corresponding key.
|
||||
for (size_t i = 0; i < mocked_desc.size();) {
|
||||
if (mocked_desc[i] == '%') {
|
||||
if (i + 3 >= mocked_desc.size()) return {};
|
||||
if (const auto idx = IdxFromHex(mocked_desc.substr(i + 1, 2))) {
|
||||
desc += keys_str[*idx];
|
||||
i += 3;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
desc += mocked_desc[i++];
|
||||
}
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
48
src/test/fuzz/util/descriptor.h
Normal file
48
src/test/fuzz/util/descriptor.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2023-present 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_FUZZ_UTIL_DESCRIPTOR_H
|
||||
#define BITCOIN_TEST_FUZZ_UTIL_DESCRIPTOR_H
|
||||
|
||||
#include <key_io.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <script/descriptor.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
/**
|
||||
* Converts a mocked descriptor string to a valid one. Every key in a mocked descriptor key is
|
||||
* represented by 2 hex characters preceded by the '%' character. We parse the two hex characters
|
||||
* as an index in a list of pre-generated keys. This list contains keys of the various types
|
||||
* accepted in descriptor keys expressions.
|
||||
*/
|
||||
class MockedDescriptorConverter {
|
||||
private:
|
||||
//! Types are raw (un)compressed pubkeys, raw xonly pubkeys, raw privkeys (WIF), xpubs, xprvs.
|
||||
static constexpr uint8_t KEY_TYPES_COUNT{6};
|
||||
//! How many keys we'll generate in total.
|
||||
static constexpr size_t TOTAL_KEYS_GENERATED{std::numeric_limits<uint8_t>::max() + 1};
|
||||
//! 256 keys of various types.
|
||||
std::array<std::string, TOTAL_KEYS_GENERATED> keys_str;
|
||||
|
||||
public:
|
||||
// We derive the type of key to generate from the 1-byte id parsed from hex.
|
||||
bool IdIsCompPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 0; }
|
||||
bool IdIsUnCompPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 1; }
|
||||
bool IdIsXOnlyPubKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 2; }
|
||||
bool IdIsConstPrivKey(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 3; }
|
||||
bool IdIsXpub(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 4; }
|
||||
bool IdIsXprv(uint8_t idx) const { return idx % KEY_TYPES_COUNT == 5; }
|
||||
|
||||
//! When initializing the target, populate the list of keys.
|
||||
void Init();
|
||||
|
||||
//! Parse an id in the keys vectors from a 2-characters hex string.
|
||||
std::optional<uint8_t> IdxFromHex(std::string_view hex_characters) const;
|
||||
|
||||
//! Get an actual descriptor string from a descriptor string whose keys were mocked.
|
||||
std::optional<std::string> GetDescriptor(std::string_view mocked_desc) const;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_TEST_FUZZ_UTIL_DESCRIPTOR_H
|
||||
Reference in New Issue
Block a user