mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-07 21:28:40 +02:00
Merge bitcoin/bitcoin#24860: Miniscript integration follow-ups
f3a50c9dfeminiscript: rename IsSane and IsSaneSubexpression to prevent misuse (Antoine Poinsot)c5fe5163dcminiscript: nit: don't return after assert(false) (Antoine Poinsot)7bbaca9d8dminiscript: explicit the threshold size computation in multi() (Antoine Poinsot)8323e4249dminiscript: add an OpCode typedef for readability (Antoine Poinsot)7a549c6c59miniscript: mark nodes with duplicate keys as insane (Antoine Poinsot)8c0f8bf7bcfuzz: add a Miniscript target for string representation roundtripping (Antoine Poinsot)be34d5077bfuzz: rename and improve the Miniscript Script roundtrip target (Antoine Poinsot)7eb70f0ac0miniscript: tiny doc fixups (Antoine Poinsot)5cea85f12cminiscript: split ValidSatisfactions from IsSane (Antoine Poinsot)a0f064dc14miniscript: introduce a CheckTimeLocksMix helper (Antoine Poinsot)ed45ee3882miniscript: use optional instead of bool/outarg (Antoine Poinsot)1ab8d89fd1miniscript: make equality operator non-recursive (Antoine Poinsot)5922c662c0scripted-diff: miniscript: rename 'nodetype' variables to 'fragment' (Antoine Poinsot)c5f65db0f0miniscript: remove a workaround for a GCC 4.8 bug (Antoine Poinsot) Pull request description: The Miniscript repository and the Miniscript integration PR here have been a moving target for the past months, and some final cleanups were done there that were not included here. I initially intended to add some small followup commits to #24148 but i think there are enough of them to be worth a followup PR on its own. Some parts of the code did not change since it was initially written in 2019, and the code could use some modernization. (Use std::optional instead of out args, remove old compiler workarounds). We refactored the helpers to be more meaningful, and also did some renaming. A new fuzz target was also added and both were merged in a single file. 2 more will be added in #24149 that will be contained in this file too. The only behaviour change in this PR is to rule out Miniscript with duplicate keys from sane Miniscripts. In a P2WSH context, signatures can be rebounded (Miniscript does not use CODESEPARATOR) and it's reasonable to assume that reusing keys across the Script drops the malleability guarantees. It was previously assumed such Miniscript would never exist in the first place since a compiler should never create them. We finally agreed that if one were to exist (say, written by hand or from a buggy compiler) it would be very confusing if an imported Miniscript descriptor (after #24148) with duplicate keys was deemed sane (ie, "safe to use") by Bitcoin Core. We now check for duplicate keys in the constructor. This is (still) joint work with Pieter Wuille. (Actually he entirely authored the cleanups and code modernization.) ACKs for top commit: sipa: utACKf3a50c9dfe(with the caveat that a lot of it is my own code) sanket1729: code review ACKf3a50c9dfe. Did not review the fuzz tests. Tree-SHA512: c043325e4936fe25e8ece4266b46119e000c6745f88cea530fed1edf01c80f03ee6f9edc83b6e9d42ca01688d184bad16bfd967c5bb8037744e726993adf3deb
This commit is contained in:
167
src/test/fuzz/miniscript.cpp
Normal file
167
src/test/fuzz/miniscript.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright (c) 2021 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 <core_io.h>
|
||||
#include <hash.h>
|
||||
#include <key.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <script/script.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
namespace {
|
||||
|
||||
//! Some pre-computed data for more efficient string roundtrips.
|
||||
struct TestData {
|
||||
typedef CPubKey Key;
|
||||
|
||||
// Precomputed public keys.
|
||||
std::vector<Key> dummy_keys;
|
||||
std::map<Key, int> dummy_key_idx_map;
|
||||
std::map<CKeyID, Key> dummy_keys_map;
|
||||
|
||||
//! Set the precomputed data.
|
||||
void Init() {
|
||||
unsigned char keydata[32] = {1};
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
keydata[31] = i;
|
||||
CKey privkey;
|
||||
privkey.Set(keydata, keydata + 32, true);
|
||||
const Key pubkey = privkey.GetPubKey();
|
||||
|
||||
dummy_keys.push_back(pubkey);
|
||||
dummy_key_idx_map.emplace(pubkey, i);
|
||||
dummy_keys_map.insert({pubkey.GetID(), pubkey});
|
||||
}
|
||||
}
|
||||
} TEST_DATA;
|
||||
|
||||
/**
|
||||
* Context to parse a Miniscript node to and from Script or text representation.
|
||||
* Uses an integer (an index in the dummy keys array from the test data) as keys in order
|
||||
* to focus on fuzzing the Miniscript nodes' test representation, not the key representation.
|
||||
*/
|
||||
struct ParserContext {
|
||||
typedef CPubKey Key;
|
||||
|
||||
bool KeyCompare(const Key& a, const Key& b) const {
|
||||
return a < b;
|
||||
}
|
||||
|
||||
std::optional<std::string> ToString(const Key& key) const
|
||||
{
|
||||
auto it = TEST_DATA.dummy_key_idx_map.find(key);
|
||||
if (it == TEST_DATA.dummy_key_idx_map.end()) return {};
|
||||
uint8_t idx = it->second;
|
||||
return HexStr(Span{&idx, 1});
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromString(I first, I last) const {
|
||||
if (last - first != 2) return {};
|
||||
auto idx = ParseHex(std::string(first, last));
|
||||
if (idx.size() != 1) return {};
|
||||
return TEST_DATA.dummy_keys[idx[0]];
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKBytes(I first, I last) const {
|
||||
Key key;
|
||||
key.Set(first, last);
|
||||
if (!key.IsValid()) return {};
|
||||
return key;
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKHBytes(I first, I last) const {
|
||||
assert(last - first == 20);
|
||||
CKeyID keyid;
|
||||
std::copy(first, last, keyid.begin());
|
||||
const auto it = TEST_DATA.dummy_keys_map.find(keyid);
|
||||
if (it == TEST_DATA.dummy_keys_map.end()) return {};
|
||||
return it->second;
|
||||
}
|
||||
} PARSER_CTX;
|
||||
|
||||
//! Context that implements naive conversion from/to script only, for roundtrip testing.
|
||||
struct ScriptParserContext {
|
||||
//! For Script roundtrip we never need the key from a key hash.
|
||||
struct Key {
|
||||
bool is_hash;
|
||||
std::vector<unsigned char> data;
|
||||
};
|
||||
|
||||
bool KeyCompare(const Key& a, const Key& b) const {
|
||||
return a.data < b.data;
|
||||
}
|
||||
|
||||
const std::vector<unsigned char>& ToPKBytes(const Key& key) const
|
||||
{
|
||||
assert(!key.is_hash);
|
||||
return key.data;
|
||||
}
|
||||
|
||||
const std::vector<unsigned char> ToPKHBytes(const Key& key) const
|
||||
{
|
||||
if (key.is_hash) return key.data;
|
||||
const auto h = Hash160(key.data);
|
||||
return {h.begin(), h.end()};
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKBytes(I first, I last) const
|
||||
{
|
||||
Key key;
|
||||
key.data.assign(first, last);
|
||||
key.is_hash = false;
|
||||
return key;
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
std::optional<Key> FromPKHBytes(I first, I last) const
|
||||
{
|
||||
Key key;
|
||||
key.data.assign(first, last);
|
||||
key.is_hash = true;
|
||||
return key;
|
||||
}
|
||||
} SCRIPT_PARSER_CONTEXT;
|
||||
|
||||
} // namespace
|
||||
|
||||
void FuzzInit()
|
||||
{
|
||||
ECC_Start();
|
||||
TEST_DATA.Init();
|
||||
}
|
||||
|
||||
/* Fuzz tests that test parsing from a string, and roundtripping via string. */
|
||||
FUZZ_TARGET_INIT(miniscript_string, FuzzInit)
|
||||
{
|
||||
FuzzedDataProvider provider(buffer.data(), buffer.size());
|
||||
auto str = provider.ConsumeRemainingBytesAsString();
|
||||
auto parsed = miniscript::FromString(str, PARSER_CTX);
|
||||
if (!parsed) return;
|
||||
|
||||
const auto str2 = parsed->ToString(PARSER_CTX);
|
||||
assert(str2);
|
||||
auto parsed2 = miniscript::FromString(*str2, PARSER_CTX);
|
||||
assert(parsed2);
|
||||
assert(*parsed == *parsed2);
|
||||
}
|
||||
|
||||
/* Fuzz tests that test parsing from a script, and roundtripping via script. */
|
||||
FUZZ_TARGET(miniscript_script)
|
||||
{
|
||||
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
||||
const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider);
|
||||
if (!script) return;
|
||||
|
||||
const auto ms = miniscript::FromScript(*script, SCRIPT_PARSER_CONTEXT);
|
||||
if (!ms) return;
|
||||
|
||||
assert(ms->ToScript(SCRIPT_PARSER_CONTEXT) == *script);
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright (c) 2022 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 <core_io.h>
|
||||
#include <hash.h>
|
||||
#include <key.h>
|
||||
#include <script/miniscript.h>
|
||||
#include <script/script.h>
|
||||
#include <span.h>
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
using miniscript::operator""_mst;
|
||||
|
||||
|
||||
struct Converter {
|
||||
typedef CPubKey Key;
|
||||
|
||||
bool ToString(const Key& key, std::string& ret) const {
|
||||
ret = HexStr(key);
|
||||
return true;
|
||||
}
|
||||
const std::vector<unsigned char> ToPKBytes(const Key& key) const {
|
||||
return {key.begin(), key.end()};
|
||||
}
|
||||
const std::vector<unsigned char> ToPKHBytes(const Key& key) const {
|
||||
const auto h = Hash160(key);
|
||||
return {h.begin(), h.end()};
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
bool FromString(I first, I last, Key& key) const {
|
||||
const auto bytes = ParseHex(std::string(first, last));
|
||||
key.Set(bytes.begin(), bytes.end());
|
||||
return key.IsValid();
|
||||
}
|
||||
template<typename I>
|
||||
bool FromPKBytes(I first, I last, CPubKey& key) const {
|
||||
key.Set(first, last);
|
||||
return key.IsValid();
|
||||
}
|
||||
template<typename I>
|
||||
bool FromPKHBytes(I first, I last, CPubKey& key) const {
|
||||
assert(last - first == 20);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const Converter CONVERTER;
|
||||
|
||||
FUZZ_TARGET(miniscript_decode)
|
||||
{
|
||||
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
||||
const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider);
|
||||
if (!script) return;
|
||||
|
||||
const auto ms = miniscript::FromScript(*script, CONVERTER);
|
||||
if (!ms) return;
|
||||
|
||||
// We can roundtrip it to its string representation.
|
||||
std::string ms_str;
|
||||
assert(ms->ToString(CONVERTER, ms_str));
|
||||
assert(*miniscript::FromString(ms_str, CONVERTER) == *ms);
|
||||
// The Script representation must roundtrip since we parsed it this way the first time.
|
||||
const CScript ms_script = ms->ToScript(CONVERTER);
|
||||
assert(ms_script == *script);
|
||||
}
|
||||
Reference in New Issue
Block a user