Skip to content

Commit

Permalink
Merge bitcoin/bitcoin#26567: Wallet: estimate the size of signed inpu…
Browse files Browse the repository at this point in the history
…ts using descriptors

10546a5 wallet: accurately account for the size of the witness stack (Antoine Poinsot)
9b7ec39 wallet: use descriptor satisfaction size to estimate inputs size (Antoine Poinsot)
8d870a9 script/signingprovider: introduce a MultiSigningProvider (Antoine Poinsot)
fa7c46b descriptor: introduce a method to get the satisfaction size (Antoine Poinsot)
bdba766 miniscript: introduce a helper to get the maximum witness size (Antoine Poinsot)
4ab382c miniscript: make GetStackSize independent of P2WSH context (Antoine Poinsot)

Pull request description:

  The wallet currently estimates the size of a signed input by doing a dry run of the signing logic. This is unnecessary since all outputs we can sign for can be represented by a descriptor, and we can derive the size of a satisfaction ("signature") directly from the descriptor itself.
  In addition, the current approach does not generalize well: dry runs of the signing logic are only possible for the most basic scripts. See for instance the discussion in #24149 around that.

  This introduces a method to get the maximum size of a satisfaction from a descriptor, and makes the wallet use that instead of the dry-run.

ACKs for top commit:
  sipa:
    utACK 10546a5
  achow101:
    re-ACK 10546a5

Tree-SHA512: 43ed1529fbd30af709d903c8c5063235e8c6a03b500bc8f144273d6184e23a53edf0fea9ef898ed57d8a40d73208b5d935cc73b94a24fad3ad3c63b3b2027174
  • Loading branch information
achow101 committed Sep 6, 2023
2 parents cf42182 + 10546a5 commit d2ccca2
Show file tree
Hide file tree
Showing 15 changed files with 507 additions and 206 deletions.
4 changes: 4 additions & 0 deletions src/consensus/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ static inline int64_t GetTransactionInputWeight(const CTxIn& txin)
// scriptWitness size is added here because witnesses and txins are split up in segwit serialization.
return ::GetSerializeSize(txin, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(txin, PROTOCOL_VERSION) + ::GetSerializeSize(txin.scriptWitness.stack, PROTOCOL_VERSION);
}
static inline int64_t GetTransactionOutputWeight(const CTxOut& txout)
{
return ::GetSerializeSize(txout, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(txout, PROTOCOL_VERSION);
}

/** Compute at which vout of the block's coinbase transaction the witness commitment occurs, or -1 if not found */
inline int GetWitnessCommitmentIndex(const CBlock& block)
Expand Down
168 changes: 167 additions & 1 deletion src/script/descriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <util/vector.h>

#include <memory>
#include <numeric>
#include <optional>
#include <string>
#include <vector>
Expand Down Expand Up @@ -706,6 +707,19 @@ class DescriptorImpl : public Descriptor
}

std::optional<OutputType> GetOutputType() const override { return std::nullopt; }

std::optional<int64_t> ScriptSize() const override { return {}; }

/** A helper for MaxSatisfactionWeight.
*
* @param use_max_sig Whether to assume ECDSA signatures will have a high-r.
* @return The maximum size of the satisfaction in raw bytes (with no witness meaning).
*/
virtual std::optional<int64_t> MaxSatSize(bool use_max_sig) const { return {}; }

std::optional<int64_t> MaxSatisfactionWeight(bool) const override { return {}; }

std::optional<int64_t> MaxSatisfactionElems() const override { return {}; }
};

/** A parsed addr(A) descriptor. */
Expand All @@ -725,6 +739,8 @@ class AddressDescriptor final : public DescriptorImpl
}
bool IsSingleType() const final { return true; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }

std::optional<int64_t> ScriptSize() const override { return GetScriptForDestination(m_destination).size(); }
};

/** A parsed raw(H) descriptor. */
Expand All @@ -746,6 +762,8 @@ class RawDescriptor final : public DescriptorImpl
}
bool IsSingleType() const final { return true; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }

std::optional<int64_t> ScriptSize() const override { return m_script.size(); }
};

/** A parsed pk(P) descriptor. */
Expand All @@ -766,6 +784,21 @@ class PKDescriptor final : public DescriptorImpl
public:
PKDescriptor(std::unique_ptr<PubkeyProvider> prov, bool xonly = false) : DescriptorImpl(Vector(std::move(prov)), "pk"), m_xonly(xonly) {}
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override {
return 1 + (m_xonly ? 32 : m_pubkey_args[0]->GetSize()) + 1;
}

std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
const auto ecdsa_sig_size = use_max_sig ? 72 : 71;
return 1 + (m_xonly ? 65 : ecdsa_sig_size);
}

std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR;
}

std::optional<int64_t> MaxSatisfactionElems() const override { return 1; }
};

/** A parsed pkh(P) descriptor. */
Expand All @@ -782,6 +815,19 @@ class PKHDescriptor final : public DescriptorImpl
PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pkh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 1 + 20 + 1 + 1; }

std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
const auto sig_size = use_max_sig ? 72 : 71;
return 1 + sig_size + 1 + m_pubkey_args[0]->GetSize();
}

std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR;
}

std::optional<int64_t> MaxSatisfactionElems() const override { return 2; }
};

/** A parsed wpkh(P) descriptor. */
Expand All @@ -798,6 +844,19 @@ class WPKHDescriptor final : public DescriptorImpl
WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "wpkh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20; }

std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
const auto sig_size = use_max_sig ? 72 : 71;
return (1 + sig_size + 1 + 33);
}

std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
return MaxSatSize(use_max_sig);
}

std::optional<int64_t> MaxSatisfactionElems() const override { return 2; }
};

/** A parsed combo(P) descriptor. */
Expand Down Expand Up @@ -842,6 +901,24 @@ class MultisigDescriptor final : public DescriptorImpl
public:
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) {}
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override {
const auto n_keys = m_pubkey_args.size();
auto op = [](int64_t acc, const std::unique_ptr<PubkeyProvider>& pk) { return acc + 1 + pk->GetSize();};
const auto pubkeys_size{std::accumulate(m_pubkey_args.begin(), m_pubkey_args.end(), int64_t{0}, op)};
return 1 + BuildScript(n_keys).size() + BuildScript(m_threshold).size() + pubkeys_size;
}

std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
const auto sig_size = use_max_sig ? 72 : 71;
return (1 + (1 + sig_size) * m_threshold);
}

std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
return *MaxSatSize(use_max_sig) * WITNESS_SCALE_FACTOR;
}

std::optional<int64_t> MaxSatisfactionElems() const override { return 1 + m_threshold; }
};

/** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */
Expand All @@ -867,6 +944,17 @@ class MultiADescriptor final : public DescriptorImpl
public:
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) {}
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override {
const auto n_keys = m_pubkey_args.size();
return (1 + 32 + 1) * n_keys + BuildScript(m_threshold).size() + 1;
}

std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
return (1 + 65) * m_threshold + (m_pubkey_args.size() - m_threshold);
}

std::optional<int64_t> MaxSatisfactionElems() const override { return m_pubkey_args.size(); }
};

/** A parsed sh(...) descriptor. */
Expand All @@ -879,16 +967,39 @@ class SHDescriptor final : public DescriptorImpl
if (ret.size()) out.scripts.emplace(CScriptID(scripts[0]), scripts[0]);
return ret;
}

bool IsSegwit() const { return m_subdescriptor_args[0]->GetOutputType() == OutputType::BECH32; }

public:
SHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "sh") {}

std::optional<OutputType> GetOutputType() const override
{
assert(m_subdescriptor_args.size() == 1);
if (m_subdescriptor_args[0]->GetOutputType() == OutputType::BECH32) return OutputType::P2SH_SEGWIT;
if (IsSegwit()) return OutputType::P2SH_SEGWIT;
return OutputType::LEGACY;
}
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20 + 1; }

std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
if (const auto sat_size = m_subdescriptor_args[0]->MaxSatSize(use_max_sig)) {
if (const auto subscript_size = m_subdescriptor_args[0]->ScriptSize()) {
// The subscript is never witness data.
const auto subscript_weight = (1 + *subscript_size) * WITNESS_SCALE_FACTOR;
// The weight depends on whether the inner descriptor is satisfied using the witness stack.
if (IsSegwit()) return subscript_weight + *sat_size;
return subscript_weight + *sat_size * WITNESS_SCALE_FACTOR;
}
}
return {};
}

std::optional<int64_t> MaxSatisfactionElems() const override {
if (const auto sub_elems = m_subdescriptor_args[0]->MaxSatisfactionElems()) return 1 + *sub_elems;
return {};
}
};

/** A parsed wsh(...) descriptor. */
Expand All @@ -905,6 +1016,26 @@ class WSHDescriptor final : public DescriptorImpl
WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }

std::optional<int64_t> MaxSatSize(bool use_max_sig) const override {
if (const auto sat_size = m_subdescriptor_args[0]->MaxSatSize(use_max_sig)) {
if (const auto subscript_size = m_subdescriptor_args[0]->ScriptSize()) {
return GetSizeOfCompactSize(*subscript_size) + *subscript_size + *sat_size;
}
}
return {};
}

std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const override {
return MaxSatSize(use_max_sig);
}

std::optional<int64_t> MaxSatisfactionElems() const override {
if (const auto sub_elems = m_subdescriptor_args[0]->MaxSatisfactionElems()) return 1 + *sub_elems;
return {};
}
};

/** A parsed tr(...) descriptor. */
Expand Down Expand Up @@ -958,6 +1089,18 @@ class TRDescriptor final : public DescriptorImpl
}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }

std::optional<int64_t> MaxSatisfactionWeight(bool) const override {
// FIXME: We assume keypath spend, which can lead to very large underestimations.
return 1 + 65;
}

std::optional<int64_t> MaxSatisfactionElems() const override {
// FIXME: See above, we assume keypath spend.
return 1;
}
};

/* We instantiate Miniscript here with a simple integer as key type.
Expand Down Expand Up @@ -1041,6 +1184,17 @@ class MiniscriptDescriptor final : public DescriptorImpl

bool IsSolvable() const override { return true; }
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override { return m_node->ScriptSize(); }

std::optional<int64_t> MaxSatSize(bool) const override {
// For Miniscript we always assume high-R ECDSA signatures.
return m_node->GetWitnessSize();
}

std::optional<int64_t> MaxSatisfactionElems() const override {
return m_node->GetStackSize();
}
};

/** A parsed rawtr(...) descriptor. */
Expand All @@ -1059,6 +1213,18 @@ class RawTRDescriptor final : public DescriptorImpl
RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; }

std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }

std::optional<int64_t> MaxSatisfactionWeight(bool) const override {
// We can't know whether there is a script path, so assume key path spend.
return 1 + 65;
}

std::optional<int64_t> MaxSatisfactionElems() const override {
// See above, we assume keypath spend.
return 1;
}
};

////////////////////////////////////////////////////////////////////////////
Expand Down
12 changes: 12 additions & 0 deletions src/script/descriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ struct Descriptor {

/** @return The OutputType of the scriptPubKey(s) produced by this descriptor. Or nullopt if indeterminate (multiple or none) */
virtual std::optional<OutputType> GetOutputType() const = 0;

/** Get the size of the scriptPubKey for this descriptor. */
virtual std::optional<int64_t> ScriptSize() const = 0;

/** Get the maximum size of a satisfaction for this descriptor, in weight units.
*
* @param use_max_sig Whether to assume ECDSA signatures will have a high-r.
*/
virtual std::optional<int64_t> MaxSatisfactionWeight(bool use_max_sig) const = 0;

/** Get the maximum size number of stack elements for satisfying this descriptor. */
virtual std::optional<int64_t> MaxSatisfactionElems() const = 0;
};

/** Parse a `descriptor` string. Included private keys are put in `out`.
Expand Down
Loading

0 comments on commit d2ccca2

Please sign in to comment.