Skip to content

Commit fa7c46b

Browse files
committed
descriptor: introduce a method to get the satisfaction size
In the wallet code, we are currently estimating the size of a signed input by doing a dry run of the signing logic. This is unnecessary as all outputs we are able to sign for can be represented by a descriptor, and we can derive the size of a satisfaction ("signature") from the descriptor itself directly. In addition, this approach does not scale: getting the size of a satisfaction through a dry run of the signing logic is only possible for the most basic scripts. This commit introduces the computation of the size of satisfaction per descriptor. It's a bit intricate for 2 main reasons: - We want to conserve the behaviour of the current dry-run logic used by the wallet that sometimes assumes ECDSA signatures will be low-r, sometimes not (when we don't create them). - We need to account for the witness discount. A single descriptor may sometimes benefit of it, sometimes not (for instance `pk()` if used as top-level versus if used inside `wsh()`).
1 parent bdba766 commit fa7c46b

File tree

5 files changed

+166
-2
lines changed

5 files changed

+166
-2
lines changed

src/script/descriptor.cpp

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <util/vector.h>
2323

2424
#include <memory>
25+
#include <numeric>
2526
#include <optional>
2627
#include <string>
2728
#include <vector>
@@ -706,6 +707,17 @@ class DescriptorImpl : public Descriptor
706707
}
707708

708709
std::optional<OutputType> GetOutputType() const override { return std::nullopt; }
710+
711+
std::optional<int64_t> ScriptSize() const override { return {}; }
712+
713+
/** A helper for MaxSatisfactionWeight.
714+
*
715+
* @param use_max_sig Whether to assume ECDSA signatures will have a high-r.
716+
* @return The maximum size of the satisfaction in raw bytes (with no witness meaning).
717+
*/
718+
virtual std::optional<int64_t> MaxSatSize(bool use_max_sig) const { return {}; }
719+
720+
std::optional<int64_t> MaxSatisfactionWeight(bool) const override { return {}; }
709721
};
710722

711723
/** A parsed addr(A) descriptor. */
@@ -725,6 +737,8 @@ class AddressDescriptor final : public DescriptorImpl
725737
}
726738
bool IsSingleType() const final { return true; }
727739
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
740+
741+
std::optional<int64_t> ScriptSize() const override { return GetScriptForDestination(m_destination).size(); }
728742
};
729743

730744
/** A parsed raw(H) descriptor. */
@@ -746,6 +760,8 @@ class RawDescriptor final : public DescriptorImpl
746760
}
747761
bool IsSingleType() const final { return true; }
748762
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
763+
764+
std::optional<int64_t> ScriptSize() const override { return m_script.size(); }
749765
};
750766

751767
/** A parsed pk(P) descriptor. */
@@ -766,6 +782,19 @@ class PKDescriptor final : public DescriptorImpl
766782
public:
767783
PKDescriptor(std::unique_ptr<PubkeyProvider> prov, bool xonly = false) : DescriptorImpl(Vector(std::move(prov)), "pk"), m_xonly(xonly) {}
768784
bool IsSingleType() const final { return true; }
785+
786+
std::optional<int64_t> ScriptSize() const override {
787+
return 1 + (m_xonly ? 32 : m_pubkey_args[0]->GetSize()) + 1;
788+
}
789+
790+
std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
791+
const auto ecdsa_sig_size = use_max_sig ? 72 : 71;
792+
return 1 + (m_xonly ? 65 : ecdsa_sig_size);
793+
}
794+
795+
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
796+
return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR;
797+
}
769798
};
770799

771800
/** A parsed pkh(P) descriptor. */
@@ -782,6 +811,17 @@ class PKHDescriptor final : public DescriptorImpl
782811
PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pkh") {}
783812
std::optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
784813
bool IsSingleType() const final { return true; }
814+
815+
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 1 + 20 + 1 + 1; }
816+
817+
std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
818+
const auto sig_size = use_max_sig ? 72 : 71;
819+
return 1 + sig_size + 1 + m_pubkey_args[0]->GetSize();
820+
}
821+
822+
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
823+
return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR;
824+
}
785825
};
786826

787827
/** A parsed wpkh(P) descriptor. */
@@ -798,6 +838,17 @@ class WPKHDescriptor final : public DescriptorImpl
798838
WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "wpkh") {}
799839
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
800840
bool IsSingleType() const final { return true; }
841+
842+
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20; }
843+
844+
std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
845+
const auto sig_size = use_max_sig ? 72 : 71;
846+
return (1 + sig_size + 1 + 33);
847+
}
848+
849+
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
850+
return MaxSatSize(use_max_sig);
851+
}
801852
};
802853

803854
/** A parsed combo(P) descriptor. */
@@ -842,6 +893,22 @@ class MultisigDescriptor final : public DescriptorImpl
842893
public:
843894
MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti" : "multi"), m_threshold(threshold), m_sorted(sorted) {}
844895
bool IsSingleType() const final { return true; }
896+
897+
std::optional<int64_t> ScriptSize() const override {
898+
const auto n_keys = m_pubkey_args.size();
899+
auto op = [](int64_t acc, const std::unique_ptr<PubkeyProvider>& pk) { return acc + 1 + pk->GetSize();};
900+
const auto pubkeys_size{std::accumulate(m_pubkey_args.begin(), m_pubkey_args.end(), int64_t{0}, op)};
901+
return 1 + BuildScript(n_keys).size() + BuildScript(m_threshold).size() + pubkeys_size;
902+
}
903+
904+
std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
905+
const auto sig_size = use_max_sig ? 72 : 71;
906+
return (1 + (1 + sig_size) * m_threshold);
907+
}
908+
909+
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
910+
return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR;
911+
}
845912
};
846913

847914
/** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */
@@ -867,6 +934,15 @@ class MultiADescriptor final : public DescriptorImpl
867934
public:
868935
MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {}
869936
bool IsSingleType() const final { return true; }
937+
938+
std::optional<int64_t> ScriptSize() const override {
939+
const auto n_keys = m_pubkey_args.size();
940+
return (1 + 32 + 1) * n_keys + BuildScript(m_threshold).size() + 1;
941+
}
942+
943+
std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
944+
return (1 + 65) * m_threshold + (m_pubkey_args.size() - m_threshold);
945+
}
870946
};
871947

872948
/** A parsed sh(...) descriptor. */
@@ -879,16 +955,34 @@ class SHDescriptor final : public DescriptorImpl
879955
if (ret.size()) out.scripts.emplace(CScriptID(scripts[0]), scripts[0]);
880956
return ret;
881957
}
958+
959+
bool IsSegwit() const { return m_subdescriptor_args[0]->GetOutputType() == OutputType::BECH32; }
960+
882961
public:
883962
SHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "sh") {}
884963

885964
std::optional<OutputType> GetOutputType() const override
886965
{
887966
assert(m_subdescriptor_args.size() == 1);
888-
if (m_subdescriptor_args[0]->GetOutputType() == OutputType::BECH32) return OutputType::P2SH_SEGWIT;
967+
if (IsSegwit()) return OutputType::P2SH_SEGWIT;
889968
return OutputType::LEGACY;
890969
}
891970
bool IsSingleType() const final { return true; }
971+
972+
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20 + 1; }
973+
974+
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
975+
if (const auto sat_size = m_subdescriptor_args[0]->MaxSatSize(use_max_sig)) {
976+
if (const auto subscript_size = m_subdescriptor_args[0]->ScriptSize()) {
977+
// The subscript is never witness data.
978+
const auto subscript_weight = (1 + *subscript_size) * WITNESS_SCALE_FACTOR;
979+
// The weight depends on whether the inner descriptor is satisfied using the witness stack.
980+
if (IsSegwit()) return subscript_weight + *sat_size;
981+
return subscript_weight + *sat_size * WITNESS_SCALE_FACTOR;
982+
}
983+
}
984+
return {};
985+
}
892986
};
893987

894988
/** A parsed wsh(...) descriptor. */
@@ -905,6 +999,21 @@ class WSHDescriptor final : public DescriptorImpl
905999
WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {}
9061000
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
9071001
bool IsSingleType() const final { return true; }
1002+
1003+
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }
1004+
1005+
std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
1006+
if (const auto sat_size = m_subdescriptor_args[0]->MaxSatSize(use_max_sig)) {
1007+
if (const auto subscript_size = m_subdescriptor_args[0]->ScriptSize()) {
1008+
return GetSizeOfCompactSize(*subscript_size) + *subscript_size + *sat_size;
1009+
}
1010+
}
1011+
return {};
1012+
}
1013+
1014+
std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
1015+
return MaxSatSize(use_max_sig);
1016+
}
9081017
};
9091018

9101019
/** A parsed tr(...) descriptor. */
@@ -958,6 +1067,13 @@ class TRDescriptor final : public DescriptorImpl
9581067
}
9591068
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
9601069
bool IsSingleType() const final { return true; }
1070+
1071+
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }
1072+
1073+
std::optional<int64_t> MaxSatisfactionWeight(bool) const override {
1074+
// FIXME: We assume keypath spend, which can lead to very large underestimations.
1075+
return 1 + 65;
1076+
}
9611077
};
9621078

9631079
/* We instantiate Miniscript here with a simple integer as key type.
@@ -1041,6 +1157,13 @@ class MiniscriptDescriptor final : public DescriptorImpl
10411157

10421158
bool IsSolvable() const override { return true; }
10431159
bool IsSingleType() const final { return true; }
1160+
1161+
std::optional<int64_t> ScriptSize() const override { return m_node->ScriptSize(); }
1162+
1163+
std::optional<int64_t> MaxSatSize(bool) const override {
1164+
// For Miniscript we always assume high-R ECDSA signatures.
1165+
return m_node->GetWitnessSize();
1166+
}
10441167
};
10451168

10461169
/** A parsed rawtr(...) descriptor. */
@@ -1059,6 +1182,13 @@ class RawTRDescriptor final : public DescriptorImpl
10591182
RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {}
10601183
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
10611184
bool IsSingleType() const final { return true; }
1185+
1186+
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }
1187+
1188+
std::optional<int64_t> MaxSatisfactionWeight(bool) const override {
1189+
// We can't know whether there is a script path, so assume key path spend.
1190+
return 1 + 65;
1191+
}
10621192
};
10631193

10641194
////////////////////////////////////////////////////////////////////////////

src/script/descriptor.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ struct Descriptor {
146146

147147
/** @return The OutputType of the scriptPubKey(s) produced by this descriptor. Or nullopt if indeterminate (multiple or none) */
148148
virtual std::optional<OutputType> GetOutputType() const = 0;
149+
150+
/** Get the size of the scriptPubKey for this descriptor. */
151+
virtual std::optional<int64_t> ScriptSize() const = 0;
152+
153+
/** Get the maximum size of a satisfaction for this descriptor, in weight units.
154+
*
155+
* @param use_max_sig Whether to assume ECDSA signatures will have a high-r.
156+
*/
157+
virtual std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const = 0;
149158
};
150159

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

0 commit comments

Comments
 (0)