Merge 710cab1d43 into merged_master (Bitcoin PR bitcoin/bitcoin#26032)

Moved the implementations of DummySignTx and DummySignInput from
spend.cpp to wallet.cpp, to more closely match upstream
This commit is contained in:
Byron Hambly 2025-04-09 11:43:36 +02:00
commit d7c9767c01
5 changed files with 112 additions and 101 deletions

View file

@ -2502,7 +2502,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
input.FillSignatureData(sigdata);
std::unique_ptr<FlatSigningProvider> keys = std::make_unique<FlatSigningProvider>();
std::unique_ptr<FlatSigningProvider> script_keys = GetSigningProvider(script, sign);
std::unique_ptr<FlatSigningProvider> script_keys = GetSigningProvider(script, /*include_private=*/sign);
if (script_keys) {
keys->Merge(std::move(*script_keys));
} else {
@ -2543,7 +2543,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
}
}
SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
SignPSBTInput(HidingSigningProvider(keys.get(), /*hide_secret=*/!sign, /*hide_origin=*/!bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
bool signed_one = PSBTInputSigned(input);
if (n_signed && (signed_one || !sign)) {
@ -2560,7 +2560,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
if (!keys) {
continue;
}
UpdatePSBTOutput(HidingSigningProvider(keys.get(), true, !bip32derivs), psbtx, i);
UpdatePSBTOutput(HidingSigningProvider(keys.get(), /*hide_secret=*/true, /*hide_origin=*/!bip32derivs), psbtx, i);
}
return TransactionError::OK;

View file

@ -34,99 +34,10 @@ using interfaces::FoundBlock;
namespace wallet {
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
// 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) if use_max_sig is true
bool DummySignInput(const SigningProvider& provider, CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, 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 or if this particular input is an external input
// to ensure a sufficient fee is attained for the requested feerate.
const CTxIn& tx_in = tx.vin[nIn];
const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(tx_in.prevout));
if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
UpdateTransaction(tx, nIn, sigdata);
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;
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(txNew, nIn, coin_control->GetInputWeight(txin.prevout))) {
return false;
}
nIn++;
continue;
}
const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
if (!provider || !DummySignInput(*provider, txNew, nIn, txout, coin_control)) {
if (!coin_control || !DummySignInput(coin_control->m_external_provider, txNew, nIn, txout, coin_control)) {
return false;
}
}
nIn++;
}
return true;
}
bool FillInputToWeight(CMutableTransaction& mtx, size_t nIn, int64_t target_weight)
{
assert(mtx.vin[nIn].scriptSig.empty());
assert(mtx.witness.vtxinwit[nIn].scriptWitness.IsNull());
int64_t txin_weight = GetTransactionInputWeight(CTransaction(mtx), nIn);
// 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);
mtx.witness.vtxinwit[nIn].scriptWitness.stack.emplace(mtx.witness.vtxinwit[nIn].scriptWitness.stack.end(), first_weight, 0);
}
add_weight -= GetSizeOfCompactSize(add_weight);
mtx.witness.vtxinwit[nIn].scriptWitness.stack.emplace(mtx.witness.vtxinwit[nIn].scriptWitness.stack.end(), add_weight, 0);
assert(GetTransactionInputWeight(CTransaction(mtx), nIn) == target_weight);
return true;
}
int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* provider, const CCoinControl* coin_control) {
int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* provider, bool can_grind_r, const CCoinControl* coin_control) {
CMutableTransaction txn;
txn.vin.push_back(CTxIn(outpoint));
if (!provider || !DummySignInput(*provider, txn, 0, txout, coin_control)) {
if (!provider || !DummySignInput(*provider, txn, 0, txout, can_grind_r, coin_control)) {
return -1;
}
return GetVirtualTransactionInputSize(CTransaction(txn));
@ -135,7 +46,7 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoin
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, const CCoinControl* coin_control)
{
const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey);
return CalculateMaximumSignedInputSize(txout, COutPoint(), provider.get(), coin_control);
return CalculateMaximumSignedInputSize(txout, COutPoint(), provider.get(), wallet->CanGrindR(), coin_control);
}
// Returns pair of vsize and weight
@ -259,6 +170,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
PreSelectedInputs result;
std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
const bool can_grind_r = wallet.CanGrindR();
for (const COutPoint& outpoint : vPresetInputs) {
int input_bytes = -1;
CTxOut txout;
@ -277,7 +189,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
}
if (input_bytes == -1) {
input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, can_grind_r, &coin_control);
// ELEMENTS: one more try to get a signed input size: for pegins,
// the outpoint is provided as external data but the information
// needed to spend is in the wallet (not the external provider,
@ -326,6 +238,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH};
const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true};
const bool can_grind_r = wallet.CanGrindR();
std::set<uint256> trusted_parents;
for (const auto& entry : wallet.mapWallet)
@ -423,7 +336,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(output.scriptPubKey);
int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl);
int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), can_grind_r, coinControl);
bool solvable = provider ? InferDescriptor(output.scriptPubKey, *provider)->IsSolvable() : false;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
@ -1211,7 +1124,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
}
// Get size of spending the change output
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, &wallet);
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, &wallet, /*coin_control=*/nullptr);
// If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
// as lower-bound to allow BnB to do it's thing
if (change_spend_size == -1) {

View file

@ -19,8 +19,8 @@ namespace wallet {
struct CRecipient;
/** Get the marginal bytes if spending the specified output from this transaction.
* Use CoinControl to determine whether to expect signature grinding when calculating the size of the input spend. */
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, const CCoinControl* coin_control = nullptr);
int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* pwallet, const CCoinControl* coin_control = nullptr);
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, const CCoinControl* coin_control);
int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* pwallet, bool can_grind_r, const CCoinControl* coin_control);
struct TxSize {
int64_t vsize{-1};
int64_t weight{-1};

View file

@ -1652,6 +1652,96 @@ 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) if use_max_sig is true
bool DummySignInput(const SigningProvider& provider, CMutableTransaction& tx, const size_t nIn, 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 or if this particular input is an external input
// to ensure a sufficient fee is attained for the requested feerate.
const CTxIn& tx_in = tx.vin[nIn];
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;
}
UpdateTransaction(tx, nIn, sigdata);
return true;
}
bool FillInputToWeight(CMutableTransaction& mtx, size_t nIn, int64_t target_weight)
{
assert(mtx.vin[nIn].scriptSig.empty());
assert(mtx.witness.vtxinwit[nIn].scriptWitness.IsNull());
int64_t txin_weight = GetTransactionInputWeight(CTransaction(mtx), nIn);
// 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);
mtx.witness.vtxinwit[nIn].scriptWitness.stack.emplace(mtx.witness.vtxinwit[nIn].scriptWitness.stack.end(), first_weight, 0);
}
add_weight -= GetSizeOfCompactSize(add_weight);
mtx.witness.vtxinwit[nIn].scriptWitness.stack.emplace(mtx.witness.vtxinwit[nIn].scriptWitness.stack.end(), add_weight, 0);
assert(GetTransactionInputWeight(CTransaction(mtx), nIn) == 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(txNew, nIn, coin_control->GetInputWeight(txin.prevout))) {
return false;
}
nIn++;
continue;
}
const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
if (!provider || !DummySignInput(*provider, txNew, nIn, txout, can_grind_r, coin_control)) {
if (!coin_control || !DummySignInput(coin_control->m_external_provider, txNew, nIn, 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();
@ -4707,6 +4797,11 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
return true;
}
bool CWallet::CanGrindR() const
{
return !IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER);
}
bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, MigrationResult& res) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{
AssertLockHeld(wallet.cs_wallet);

View file

@ -1042,6 +1042,9 @@ public:
std::map<uint256, std::pair<CAsset, CAsset> > GetReissuanceTokenTypes() const;
// END ELEMENTS
//! Whether the (external) signer performs R-value signature grinding
bool CanGrindR() const;
};
/**
@ -1099,7 +1102,7 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
//! Remove wallet name from persistent configuration so it will not be loaded on startup.
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
bool DummySignInput(const SigningProvider& provider, CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, const CCoinControl* coin_control = nullptr);
bool DummySignInput(const SigningProvider& provider, CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, bool can_grind_r, const CCoinControl* coin_control);
bool FillInputToWeight(CMutableTransaction& mtx, size_t nIn, int64_t target_weight);