Skip to content

Commit 10546a5

Browse files
committed
wallet: accurately account for the size of the witness stack
When estimating the maximum size of an input, we were assuming the number of elements on the witness stack could be encode in a single byte. This is a valid approximation for all the descriptors we support (including P2WSH Miniscript ones), but may not hold anymore once we support Miniscript within Taproot descriptors (since the max standard witness stack size of 100 gets lifted). It's a low-hanging fruit to account for it correctly, so just do it now.
1 parent 9b7ec39 commit 10546a5

File tree

6 files changed

+58
-15
lines changed

6 files changed

+58
-15
lines changed

src/script/descriptor.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,8 @@ class DescriptorImpl : public Descriptor
718718
virtual std::optional<int64_t> MaxSatSize(bool use_max_sig) const { return {}; }
719719

720720
std::optional<int64_t> MaxSatisfactionWeight(bool) const override { return {}; }
721+
722+
std::optional<int64_t> MaxSatisfactionElems() const override { return {}; }
721723
};
722724

723725
/** A parsed addr(A) descriptor. */
@@ -795,6 +797,8 @@ class PKDescriptor final : public DescriptorImpl
795797
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
796798
return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR;
797799
}
800+
801+
std::optional<int64_t> MaxSatisfactionElems() const override { return 1; }
798802
};
799803

800804
/** A parsed pkh(P) descriptor. */
@@ -822,6 +826,8 @@ class PKHDescriptor final : public DescriptorImpl
822826
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
823827
return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR;
824828
}
829+
830+
std::optional<int64_t> MaxSatisfactionElems() const override { return 2; }
825831
};
826832

827833
/** A parsed wpkh(P) descriptor. */
@@ -849,6 +855,8 @@ class WPKHDescriptor final : public DescriptorImpl
849855
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
850856
return MaxSatSize(use_max_sig);
851857
}
858+
859+
std::optional<int64_t> MaxSatisfactionElems() const override { return 2; }
852860
};
853861

854862
/** A parsed combo(P) descriptor. */
@@ -909,6 +917,8 @@ class MultisigDescriptor final : public DescriptorImpl
909917
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
910918
return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR;
911919
}
920+
921+
std::optional<int64_t> MaxSatisfactionElems() const override { return 1 + m_threshold; }
912922
};
913923

914924
/** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */
@@ -943,6 +953,8 @@ class MultiADescriptor final : public DescriptorImpl
943953
std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
944954
return (1 + 65) * m_threshold + (m_pubkey_args.size() - m_threshold);
945955
}
956+
957+
std::optional<int64_t> MaxSatisfactionElems() const override { return m_pubkey_args.size(); }
946958
};
947959

948960
/** A parsed sh(...) descriptor. */
@@ -983,6 +995,11 @@ class SHDescriptor final : public DescriptorImpl
983995
}
984996
return {};
985997
}
998+
999+
std::optional<int64_t> MaxSatisfactionElems() const override {
1000+
if (const auto sub_elems = m_subdescriptor_args[0]->MaxSatisfactionElems()) return 1 + *sub_elems;
1001+
return {};
1002+
}
9861003
};
9871004

9881005
/** A parsed wsh(...) descriptor. */
@@ -1014,6 +1031,11 @@ class WSHDescriptor final : public DescriptorImpl
10141031
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
10151032
return MaxSatSize(use_max_sig);
10161033
}
1034+
1035+
std::optional<int64_t> MaxSatisfactionElems() const override {
1036+
if (const auto sub_elems = m_subdescriptor_args[0]->MaxSatisfactionElems()) return 1 + *sub_elems;
1037+
return {};
1038+
}
10171039
};
10181040

10191041
/** A parsed tr(...) descriptor. */
@@ -1074,6 +1096,11 @@ class TRDescriptor final : public DescriptorImpl
10741096
// FIXME: We assume keypath spend, which can lead to very large underestimations.
10751097
return 1 + 65;
10761098
}
1099+
1100+
std::optional<int64_t> MaxSatisfactionElems() const override {
1101+
// FIXME: See above, we assume keypath spend.
1102+
return 1;
1103+
}
10771104
};
10781105

10791106
/* We instantiate Miniscript here with a simple integer as key type.
@@ -1164,6 +1191,10 @@ class MiniscriptDescriptor final : public DescriptorImpl
11641191
// For Miniscript we always assume high-R ECDSA signatures.
11651192
return m_node->GetWitnessSize();
11661193
}
1194+
1195+
std::optional<int64_t> MaxSatisfactionElems() const override {
1196+
return m_node->GetStackSize();
1197+
}
11671198
};
11681199

11691200
/** A parsed rawtr(...) descriptor. */
@@ -1189,6 +1220,11 @@ class RawTRDescriptor final : public DescriptorImpl
11891220
// We can't know whether there is a script path, so assume key path spend.
11901221
return 1 + 65;
11911222
}
1223+
1224+
std::optional<int64_t> MaxSatisfactionElems() const override {
1225+
// See above, we assume keypath spend.
1226+
return 1;
1227+
}
11921228
};
11931229

11941230
////////////////////////////////////////////////////////////////////////////

src/script/descriptor.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ struct Descriptor {
155155
* @param use_max_sig Whether to assume ECDSA signatures will have a high-r.
156156
*/
157157
virtual std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const = 0;
158+
159+
/** Get the maximum size number of stack elements for satisfying this descriptor. */
160+
virtual std::optional<int64_t> MaxSatisfactionElems() const = 0;
158161
};
159162

160163
/** Parse a `descriptor` string. Included private keys are put in `out`.

src/test/descriptor_tests.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
154154
const bool is_nontop_or_nonsolvable{!parse_priv->IsSolvable() || !parse_priv->GetOutputType()};
155155
const auto max_sat_maxsig{parse_priv->MaxSatisfactionWeight(true)};
156156
const auto max_sat_nonmaxsig{parse_priv->MaxSatisfactionWeight(true)};
157-
const bool is_input_size_info_set{max_sat_maxsig && max_sat_nonmaxsig};
157+
const auto max_elems{parse_priv->MaxSatisfactionElems()};
158+
const bool is_input_size_info_set{max_sat_maxsig && max_sat_nonmaxsig && max_elems};
158159
BOOST_CHECK_MESSAGE(is_input_size_info_set || is_nontop_or_nonsolvable, prv);
159160

160161
// The ScriptSize() must match the size of the Script string. (ScriptSize() is set for all descs but 'combo()'.)

src/test/fuzz/descriptor_parse.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,10 @@ static void TestDescriptor(const Descriptor& desc, FlatSigningProvider& sig_prov
139139

140140
const auto max_sat_maxsig{desc.MaxSatisfactionWeight(true)};
141141
const auto max_sat_nonmaxsig{desc.MaxSatisfactionWeight(true)};
142+
const auto max_elems{desc.MaxSatisfactionElems()};
142143
// We must be able to estimate the max satisfaction size for any solvable descriptor (but combo).
143144
const bool is_nontop_or_nonsolvable{!is_solvable || !desc.GetOutputType()};
144-
const bool is_input_size_info_set{max_sat_maxsig && max_sat_nonmaxsig};
145+
const bool is_input_size_info_set{max_sat_maxsig && max_sat_nonmaxsig && max_elems};
145146
assert(is_input_size_info_set || is_nontop_or_nonsolvable);
146147
}
147148

src/wallet/spend.cpp

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,20 @@ static std::optional<int64_t> MaxInputWeight(const Descriptor& desc, const std::
5959
const CCoinControl* coin_control, const bool tx_is_segwit,
6060
const bool can_grind_r) {
6161
if (const auto sat_weight = desc.MaxSatisfactionWeight(!can_grind_r || UseMaxSig(txin, coin_control))) {
62-
const bool is_segwit = IsSegwit(desc);
63-
// Account for the size of the scriptsig and the number of elements on the witness stack. Note
64-
// that if any input in the transaction is spending a witness program, we need to specify the
65-
// witness stack size for every input regardless of whether it is segwit itself.
66-
// NOTE: this also works in case of mixed scriptsig-and-witness such as in p2sh-wrapped segwit v0
67-
// outputs. In this case the size of the scriptsig length will always be one (since the redeemScript
68-
// is always a push of the witness program in this case, which is smaller than 253 bytes).
69-
const int64_t scriptsig_len = is_segwit ? 1 : GetSizeOfCompactSize(*sat_weight / WITNESS_SCALE_FACTOR);
70-
// FIXME: use the real number of stack elements instead of the "1" placeholder.
71-
const int64_t witstack_len = is_segwit ? GetSizeOfCompactSize(1) : (tx_is_segwit ? 1 : 0);
72-
// previous txid + previous vout + sequence + scriptsig len + witstack size + scriptsig or witness
73-
// NOTE: sat_weight already accounts for the witness discount accordingly.
74-
return (32 + 4 + 4 + scriptsig_len) * WITNESS_SCALE_FACTOR + witstack_len + *sat_weight;
62+
if (const auto elems_count = desc.MaxSatisfactionElems()) {
63+
const bool is_segwit = IsSegwit(desc);
64+
// Account for the size of the scriptsig and the number of elements on the witness stack. Note
65+
// that if any input in the transaction is spending a witness program, we need to specify the
66+
// witness stack size for every input regardless of whether it is segwit itself.
67+
// NOTE: this also works in case of mixed scriptsig-and-witness such as in p2sh-wrapped segwit v0
68+
// outputs. In this case the size of the scriptsig length will always be one (since the redeemScript
69+
// is always a push of the witness program in this case, which is smaller than 253 bytes).
70+
const int64_t scriptsig_len = is_segwit ? 1 : GetSizeOfCompactSize(*sat_weight / WITNESS_SCALE_FACTOR);
71+
const int64_t witstack_len = is_segwit ? GetSizeOfCompactSize(*elems_count) : (tx_is_segwit ? 1 : 0);
72+
// previous txid + previous vout + sequence + scriptsig len + witstack size + scriptsig or witness
73+
// NOTE: sat_weight already accounts for the witness discount accordingly.
74+
return (32 + 4 + 4 + scriptsig_len) * WITNESS_SCALE_FACTOR + witstack_len + *sat_weight;
75+
}
7576
}
7677

7778
return {};

src/wallet/test/walletload_tests.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class DummyDescriptor final : public Descriptor {
3333
void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const override {}
3434
std::optional<int64_t> ScriptSize() const override { return {}; }
3535
std::optional<int64_t> MaxSatisfactionWeight(bool) const override { return {}; }
36+
std::optional<int64_t> MaxSatisfactionElems() const override { return {}; }
3637
};
3738

3839
BOOST_FIXTURE_TEST_CASE(wallet_load_descriptors, TestingSetup)

0 commit comments

Comments
 (0)