diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 727cf6118f4..2f3f2c7a1dc 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -718,6 +718,8 @@ public: virtual std::optional MaxSatSize(bool use_max_sig) const { return {}; } std::optional MaxSatisfactionWeight(bool) const override { return {}; } + + std::optional MaxSatisfactionElems() const override { return {}; } }; /** A parsed addr(A) descriptor. */ @@ -795,6 +797,8 @@ public: std::optional MaxSatisfactionWeight(bool use_max_sig) const override { return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR; } + + std::optional MaxSatisfactionElems() const override { return 1; } }; /** A parsed pkh(P) descriptor. */ @@ -822,6 +826,8 @@ public: std::optional MaxSatisfactionWeight(bool use_max_sig) const override { return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR; } + + std::optional MaxSatisfactionElems() const override { return 2; } }; /** A parsed wpkh(P) descriptor. */ @@ -849,6 +855,8 @@ public: std::optional MaxSatisfactionWeight(bool use_max_sig) const override { return MaxSatSize(use_max_sig); } + + std::optional MaxSatisfactionElems() const override { return 2; } }; /** A parsed combo(P) descriptor. */ @@ -909,6 +917,8 @@ public: std::optional MaxSatisfactionWeight(bool use_max_sig) const override { return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR; } + + std::optional MaxSatisfactionElems() const override { return 1 + m_threshold; } }; /** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */ @@ -943,6 +953,8 @@ public: std::optional MaxSatSize(bool use_max_sig) const override { return (1 + 65) * m_threshold + (m_pubkey_args.size() - m_threshold); } + + std::optional MaxSatisfactionElems() const override { return m_pubkey_args.size(); } }; /** A parsed sh(...) descriptor. */ @@ -983,6 +995,11 @@ public: } return {}; } + + std::optional MaxSatisfactionElems() const override { + if (const auto sub_elems = m_subdescriptor_args[0]->MaxSatisfactionElems()) return 1 + *sub_elems; + return {}; + } }; /** A parsed wsh(...) descriptor. */ @@ -1014,6 +1031,11 @@ public: std::optional MaxSatisfactionWeight(bool use_max_sig) const override { return MaxSatSize(use_max_sig); } + + std::optional MaxSatisfactionElems() const override { + if (const auto sub_elems = m_subdescriptor_args[0]->MaxSatisfactionElems()) return 1 + *sub_elems; + return {}; + } }; /** A parsed tr(...) descriptor. */ @@ -1074,6 +1096,11 @@ public: // FIXME: We assume keypath spend, which can lead to very large underestimations. return 1 + 65; } + + std::optional MaxSatisfactionElems() const override { + // FIXME: See above, we assume keypath spend. + return 1; + } }; /* We instantiate Miniscript here with a simple integer as key type. @@ -1164,6 +1191,10 @@ public: // For Miniscript we always assume high-R ECDSA signatures. return m_node->GetWitnessSize(); } + + std::optional MaxSatisfactionElems() const override { + return m_node->GetStackSize(); + } }; /** A parsed rawtr(...) descriptor. */ @@ -1189,6 +1220,11 @@ public: // We can't know whether there is a script path, so assume key path spend. return 1 + 65; } + + std::optional MaxSatisfactionElems() const override { + // See above, we assume keypath spend. + return 1; + } }; //////////////////////////////////////////////////////////////////////////// diff --git a/src/script/descriptor.h b/src/script/descriptor.h index 32f01c91549..caa5d1608d4 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -155,6 +155,9 @@ struct Descriptor { * @param use_max_sig Whether to assume ECDSA signatures will have a high-r. */ virtual std::optional MaxSatisfactionWeight(bool use_max_sig) const = 0; + + /** Get the maximum size number of stack elements for satisfying this descriptor. */ + virtual std::optional MaxSatisfactionElems() const = 0; }; /** Parse a `descriptor` string. Included private keys are put in `out`. diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index c2c3988f0d4..3a30ef453ec 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -154,7 +154,8 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int const bool is_nontop_or_nonsolvable{!parse_priv->IsSolvable() || !parse_priv->GetOutputType()}; const auto max_sat_maxsig{parse_priv->MaxSatisfactionWeight(true)}; const auto max_sat_nonmaxsig{parse_priv->MaxSatisfactionWeight(true)}; - const bool is_input_size_info_set{max_sat_maxsig && max_sat_nonmaxsig}; + const auto max_elems{parse_priv->MaxSatisfactionElems()}; + const bool is_input_size_info_set{max_sat_maxsig && max_sat_nonmaxsig && max_elems}; BOOST_CHECK_MESSAGE(is_input_size_info_set || is_nontop_or_nonsolvable, prv); // The ScriptSize() must match the size of the Script string. (ScriptSize() is set for all descs but 'combo()'.) diff --git a/src/test/fuzz/descriptor_parse.cpp b/src/test/fuzz/descriptor_parse.cpp index a866cdca9aa..26c219d6c8a 100644 --- a/src/test/fuzz/descriptor_parse.cpp +++ b/src/test/fuzz/descriptor_parse.cpp @@ -139,9 +139,10 @@ static void TestDescriptor(const Descriptor& desc, FlatSigningProvider& sig_prov 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}; + 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); } diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 81dd0331776..750b6c100b7 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -59,19 +59,20 @@ static std::optional MaxInputWeight(const Descriptor& desc, const std:: const CCoinControl* coin_control, const bool tx_is_segwit, const bool can_grind_r) { if (const auto sat_weight = desc.MaxSatisfactionWeight(!can_grind_r || UseMaxSig(txin, coin_control))) { - const bool is_segwit = IsSegwit(desc); - // Account for the size of the scriptsig and the number of elements on the witness stack. Note - // that if any input in the transaction is spending a witness program, we need to specify the - // witness stack size for every input regardless of whether it is segwit itself. - // NOTE: this also works in case of mixed scriptsig-and-witness such as in p2sh-wrapped segwit v0 - // outputs. In this case the size of the scriptsig length will always be one (since the redeemScript - // is always a push of the witness program in this case, which is smaller than 253 bytes). - const int64_t scriptsig_len = is_segwit ? 1 : GetSizeOfCompactSize(*sat_weight / WITNESS_SCALE_FACTOR); - // FIXME: use the real number of stack elements instead of the "1" placeholder. - const int64_t witstack_len = is_segwit ? GetSizeOfCompactSize(1) : (tx_is_segwit ? 1 : 0); - // previous txid + previous vout + sequence + scriptsig len + witstack size + scriptsig or witness - // NOTE: sat_weight already accounts for the witness discount accordingly. - return (32 + 4 + 4 + scriptsig_len) * WITNESS_SCALE_FACTOR + witstack_len + *sat_weight; + if (const auto elems_count = desc.MaxSatisfactionElems()) { + const bool is_segwit = IsSegwit(desc); + // Account for the size of the scriptsig and the number of elements on the witness stack. Note + // that if any input in the transaction is spending a witness program, we need to specify the + // witness stack size for every input regardless of whether it is segwit itself. + // NOTE: this also works in case of mixed scriptsig-and-witness such as in p2sh-wrapped segwit v0 + // outputs. In this case the size of the scriptsig length will always be one (since the redeemScript + // is always a push of the witness program in this case, which is smaller than 253 bytes). + const int64_t scriptsig_len = is_segwit ? 1 : GetSizeOfCompactSize(*sat_weight / WITNESS_SCALE_FACTOR); + const int64_t witstack_len = is_segwit ? GetSizeOfCompactSize(*elems_count) : (tx_is_segwit ? 1 : 0); + // previous txid + previous vout + sequence + scriptsig len + witstack size + scriptsig or witness + // NOTE: sat_weight already accounts for the witness discount accordingly. + return (32 + 4 + 4 + scriptsig_len) * WITNESS_SCALE_FACTOR + witstack_len + *sat_weight; + } } return {}; diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp index 1c6f8c5cba5..302db455b19 100644 --- a/src/wallet/test/walletload_tests.cpp +++ b/src/wallet/test/walletload_tests.cpp @@ -33,6 +33,7 @@ public: void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const override {} std::optional ScriptSize() const override { return {}; } std::optional MaxSatisfactionWeight(bool) const override { return {}; } + std::optional MaxSatisfactionElems() const override { return {}; } }; BOOST_FIXTURE_TEST_CASE(wallet_load_descriptors, TestingSetup)