Merge bitcoin/bitcoin#27997: Descriptors: rule out unspendable miniscript descriptors

c7db88af71 descriptor: assert we never parse a sane miniscript with no pubkey (Antoine Poinsot)
a49402a9ec qa: make sure we don't let unspendable Miniscript descriptors be imported (Antoine Poinsot)
639e3b6c97 descriptor: refuse to parse unspendable miniscript descriptors (Antoine Poinsot)
e3280eae1b miniscript: make GetStackSize() and GetOps() return optionals (Antoine Poinsot)

Pull request description:

  `IsSane()` in Miniscript does not ensure a Script is actually spendable. This is an issue as we would accept any sane Miniscript when parsing a descriptor. Fix this by explicitly checking a Miniscript descriptor is both sane and spendable when parsing it.

  This bug was exposed due to a check added in #22838 (https://github.com/bitcoin/bitcoin/pull/22838#discussion_r1226859880) that triggered a fuzz crash (https://github.com/bitcoin/bitcoin/pull/22838#issuecomment-1612510057).

ACKs for top commit:
  sipa:
    utACK c7db88af71
  achow101:
    ACK c7db88af71

Tree-SHA512: e79bc9f7842e98a4e8f358f05811fca51b15b4b80a171c0d2b17cf4bb1f578a18e4397bc2ece9817d392e0de0196ee6a054b7318441fd3566dd22e1f03eb64a5
This commit is contained in:
Andrew Chow
2023-07-17 19:08:56 -04:00
5 changed files with 48 additions and 14 deletions

View File

@@ -943,7 +943,8 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
assert(decoded->ToScript(PARSER_CTX) == script);
assert(decoded->GetType() == node->GetType());
if (provider.ConsumeBool() && node->GetOps() < MAX_OPS_PER_SCRIPT && node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
const auto node_ops{node->GetOps()};
if (provider.ConsumeBool() && node_ops && *node_ops < MAX_OPS_PER_SCRIPT && node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) {
// Optionally pad the script with OP_NOPs to max op the ops limit of the constructed script.
// This makes the script obviously not actually miniscript-compatible anymore, but the
// signatures constructed in this test don't commit to the script anyway, so the same
@@ -954,7 +955,7 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
// Do not pad more than what would cause MAX_STANDARD_P2WSH_SCRIPT_SIZE to be reached, however,
// as that also invalidates scripts.
int add = std::min<int>(
MAX_OPS_PER_SCRIPT - node->GetOps(),
MAX_OPS_PER_SCRIPT - *node_ops,
MAX_STANDARD_P2WSH_SCRIPT_SIZE - node->ScriptSize());
for (int i = 0; i < add; ++i) script.push_back(OP_NOP);
}
@@ -972,7 +973,7 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider)
if (nonmal_success) {
// Non-malleable satisfactions are bounded by GetStackSize().
assert(witness_nonmal.stack.size() <= node->GetStackSize());
assert(witness_nonmal.stack.size() <= *node->GetStackSize());
// If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it.
assert(mal_success);
assert(witness_nonmal.stack == witness_mal.stack);