Repository: bitcoin/bitcoin
File: src/test/fuzz/descriptor_parse.cpp (C++)

Code before:

    const auto max_sat_maxsig{desc.MaxSatisfactionWeight(true)};
    const auto max_sat_nonmaxsig{desc.MaxSatisfactionWeight(true)};
    const auto max_elems{desc.MaxSatisfactionElems()};
    // We must be able to estimate the max satisfaction size for any solvable descriptor (but combo).
    const bool is_nontop_or_nonsolvable{!*is_solvable || !desc.GetOutputType()};
    const bool is_input_size_info_set{max_sat_maxsig && max_sat_nonmaxsig && max_elems};
    assert(is_input_size_info_set || is_nontop_or_nonsolvable);
}

void initialize_descriptor_parse()
{
    static ECC_Context ecc_context{};
    SelectParams(ChainType::MAIN);
}

void initialize_mocked_descriptor_parse()
{
    initialize_descriptor_parse();
    MOCKED_DESC_CONVERTER.Init();
}

/// Deriving "expensive" descriptors will consume useful fuzz compute. The
/// compute is better spent on a smaller subset of descriptors, which still
/// covers all real end-user settings.
static bool IsTooExpensive(std::span<const uint8_t> buffer)
{
    // Key derivation is expensive. Deriving deep derivation paths takes a lot of compute and we'd
    // rather spend time elsewhere in this target, like on the actual descriptor syntax. So rule
    // out strings which could correspond to a descriptor containing a too large derivation path.
    if (HasDeepDerivPath(buffer)) return true;

    // Some fragments can take a virtually unlimited number of sub-fragments (thresh, multi_a) but
    // may perform quadratic operations on them. Limit the number of sub-fragments per fragment.
    if (HasTooManySubFrag(buffer)) return true;

    // The script building logic performs quadratic copies in the number of nested wrappers. Limit
    // the number of nested wrappers per fragment.
    if (HasTooManyWrappers(buffer)) return true;

    // If any suspected leaf is too large, it will likely not represent a valid
    // use-case. Also, possible base58 parsing in the leaf is quadratic. So
    // limit the leaf size.
    if (HasTooLargeLeafSize(buffer)) return true;

    return false;
}

FUZZ_TARGET(mocked_descriptor_parse, .init = initialize_mocked_descriptor_parse)
{
    if (IsTooExpensive(buffer)) return;

Review comment (performance):
fa3fe1d7f8d857112b8c5b1a991fe25b3ded9324: feel free to ignore: Perhaps we could move the `IsTooExpensive` function from `fuzz/scriptpubkeyman.cpp` to `util` and use it here? It would avoid some duplicated code.

Code after:

    const auto max_sat_maxsig{desc.MaxSatisfactionWeight(true)};
    const auto max_sat_nonmaxsig{desc.MaxSatisfactionWeight(true)};
    const auto max_elems{desc.MaxSatisfactionElems()};
    // We must be able to estimate the max satisfaction size for any solvable descriptor (but combo).
    const bool is_nontop_or_nonsolvable{!*is_solvable || !desc.GetOutputType()};
    const bool is_input_size_info_set{max_sat_maxsig && max_sat_nonmaxsig && max_elems};
    assert(is_input_size_info_set || is_nontop_or_nonsolvable);
}

void initialize_descriptor_parse()
{
    static ECC_Context ecc_context{};
    SelectParams(ChainType::MAIN);
}

void initialize_mocked_descriptor_parse()
{
    initialize_descriptor_parse();
    MOCKED_DESC_CONVERTER.Init();
}

FUZZ_TARGET(mocked_descriptor_parse, .init = initialize_mocked_descriptor_parse)
{
    const std::string mocked_descriptor{buffer.begin(), buffer.end()};
    if (const auto descriptor = MOCKED_DESC_CONVERTER.GetDescriptor(mocked_descriptor)) {
        if (IsTooExpensive(MakeUCharSpan(*descriptor))) return;
        FlatSigningProvider signing_provider;
        std::string error;
        const auto desc = Parse(*descriptor, signing_provider, error);
        std::optional<bool> is_ranged;
        std::optional<bool> is_solvable;
        for (const auto& d : desc) {
            assert(d);
            TestDescriptor(*d, signing_provider, error, is_ranged, is_solvable);
        }
    }
}

FUZZ_TARGET(descriptor_parse, .init = initialize_descriptor_parse)
{
    if (IsTooExpensive(buffer)) return;

    const std::string descriptor(buffer.begin(), buffer.end());
    FlatSigningProvider signing_provider;
    std::string error;
    for (const bool require_checksum : {true, false}) {
        const auto desc = Parse(descriptor, signing_provider, error, require_checksum);
        std::optional<bool> is_ranged;
        std::optional<bool> is_solvable;
        for (const auto& d : desc) {