Merge bitcoin/bitcoin#26567: Wallet: estimate the size of signed inputs using descriptors

10546a569c wallet: accurately account for the size of the witness stack (Antoine Poinsot)
9b7ec393b8 wallet: use descriptor satisfaction size to estimate inputs size (Antoine Poinsot)
8d870a9873 script/signingprovider: introduce a MultiSigningProvider (Antoine Poinsot)
fa7c46b503 descriptor: introduce a method to get the satisfaction size (Antoine Poinsot)
bdba7667d2 miniscript: introduce a helper to get the maximum witness size (Antoine Poinsot)
4ab382c2cd 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 10546a569c
  achow101:
    re-ACK 10546a569c

Tree-SHA512: 43ed1529fbd30af709d903c8c5063235e8c6a03b500bc8f144273d6184e23a53edf0fea9ef898ed57d8a40d73208b5d935cc73b94a24fad3ad3c63b3b2027174
This commit is contained in:
Andrew Chow
2023-09-06 13:23:12 -04:00
15 changed files with 507 additions and 206 deletions

View File

@@ -1703,96 +1703,6 @@ void CWallet::InitWalletFlags(uint64_t flags)
if (!LoadWalletFlags(flags)) assert(false);
}
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
// or a max-sized low-S signature (e.g. 72 bytes) depending on coin_control
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool can_grind_r, const CCoinControl* coin_control)
{
// Fill in dummy signatures for fee calculation.
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
// Use max sig if watch only inputs were used, if this particular input is an external input,
// or if this wallet uses an external signer, to ensure a sufficient fee is attained for the requested feerate.
const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(tx_in.prevout) || !can_grind_r);
if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
UpdateInput(tx_in, sigdata);
return true;
}
bool FillInputToWeight(CTxIn& txin, int64_t target_weight)
{
assert(txin.scriptSig.empty());
assert(txin.scriptWitness.IsNull());
int64_t txin_weight = GetTransactionInputWeight(txin);
// Do nothing if the weight that should be added is less than the weight that already exists
if (target_weight < txin_weight) {
return false;
}
if (target_weight == txin_weight) {
return true;
}
// Subtract current txin weight, which should include empty witness stack
int64_t add_weight = target_weight - txin_weight;
assert(add_weight > 0);
// We will want to subtract the size of the Compact Size UInt that will also be serialized.
// However doing so when the size is near a boundary can result in a problem where it is not
// possible to have a stack element size and combination to exactly equal a target.
// To avoid this possibility, if the weight to add is less than 10 bytes greater than
// a boundary, the size will be split so that 2/3rds will be in one stack element, and
// the remaining 1/3rd in another. Using 3rds allows us to avoid additional boundaries.
// 10 bytes is used because that accounts for the maximum size. This does not need to be super precise.
if ((add_weight >= 253 && add_weight < 263)
|| (add_weight > std::numeric_limits<uint16_t>::max() && add_weight <= std::numeric_limits<uint16_t>::max() + 10)
|| (add_weight > std::numeric_limits<uint32_t>::max() && add_weight <= std::numeric_limits<uint32_t>::max() + 10)) {
int64_t first_weight = add_weight / 3;
add_weight -= first_weight;
first_weight -= GetSizeOfCompactSize(first_weight);
txin.scriptWitness.stack.emplace(txin.scriptWitness.stack.end(), first_weight, 0);
}
add_weight -= GetSizeOfCompactSize(add_weight);
txin.scriptWitness.stack.emplace(txin.scriptWitness.stack.end(), add_weight, 0);
assert(GetTransactionInputWeight(txin) == target_weight);
return true;
}
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const
{
// Fill in dummy signatures for fee calculation.
int nIn = 0;
const bool can_grind_r = CanGrindR();
for (const auto& txout : txouts)
{
CTxIn& txin = txNew.vin[nIn];
// If weight was provided, fill the input to that weight
if (coin_control && coin_control->HasInputWeight(txin.prevout)) {
if (!FillInputToWeight(txin, coin_control->GetInputWeight(txin.prevout))) {
return false;
}
nIn++;
continue;
}
const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
if (!provider || !DummySignInput(*provider, txin, txout, can_grind_r, coin_control)) {
if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, can_grind_r, coin_control)) {
return false;
}
}
nIn++;
}
return true;
}
bool CWallet::ImportScripts(const std::set<CScript> scripts, int64_t timestamp)
{
auto spk_man = GetLegacyScriptPubKeyMan();