Skip to content

Commit 710cab1

Browse files
committed
Merge bitcoin/bitcoin#26032: wallet: skip R-value signature grinding for external signers
807de2c wallet: skip R-value grinding for external signers (Sjors Provoost) 72b763e wallet: annotate bools in descriptor SPKM FillPSBT() (Sjors Provoost) Pull request description: When producing a dummy signature for the purpose of estimating the transaction fee, do not assume an external signer performs R-value grinding on the signature. In particular, this avoids a scenario where the fee rate is 1 sat / vbyte and a transaction with a 72 byte signature is not accepted into our mempool. Suggested testing: 1. On master, launch with `-signet` and create an external signer wallet using e.g. a Trezor and HWI, see [guide](https://github.com/bitcoin/bitcoin/blob/master/doc/external-signer.md#example-usage) (with the GUI it should "just work" once you have the HWI path configured). 2. Create a few addresses and fund them from the faucet: https://signet.bc-2.jp/ (wait for confirmation) 3. Create another address, and now send the entire wallet to it, set the fee to 1 sat/byte 4. Most likely this transaction never gets broadcast and you won't see it on the [signet explorer](https://explorer.bc-2.jp) 5. With this PR, try again. 6. Check the explorer and inspect the transaction. Each input witness starts with either `30440220` (R has 32 bytes) or `30440221` (R has 33 bytes). See this explainer for [DER encoding](https://bitcoin.stackexchange.com/questions/92680/what-are-the-der-signature-and-sec-format). Fixes #26030 ACKs for top commit: S3RK: ACK 807de2c achow101: ACK 807de2c furszy: ACK 807de2c ishaanam: utACK 807de2c Tree-SHA512: 64f626a3030ef0ab1e43af86d8fba113151512561baf425e6e5182af53df3a64fa9e85c7f67bf4ed15b5ad6e5d5afc7fbba8b6e1f3bad388e48db51cb9446074
2 parents 82793f1 + 807de2c commit 710cab1

File tree

5 files changed

+27
-16
lines changed

5 files changed

+27
-16
lines changed

src/wallet/scriptpubkeyman.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2495,7 +2495,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
24952495
input.FillSignatureData(sigdata);
24962496

24972497
std::unique_ptr<FlatSigningProvider> keys = std::make_unique<FlatSigningProvider>();
2498-
std::unique_ptr<FlatSigningProvider> script_keys = GetSigningProvider(script, sign);
2498+
std::unique_ptr<FlatSigningProvider> script_keys = GetSigningProvider(script, /*include_private=*/sign);
24992499
if (script_keys) {
25002500
keys->Merge(std::move(*script_keys));
25012501
} else {
@@ -2536,7 +2536,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
25362536
}
25372537
}
25382538

2539-
SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
2539+
SignPSBTInput(HidingSigningProvider(keys.get(), /*hide_secret=*/!sign, /*hide_origin=*/!bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
25402540

25412541
bool signed_one = PSBTInputSigned(input);
25422542
if (n_signed && (signed_one || !sign)) {
@@ -2553,7 +2553,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
25532553
if (!keys) {
25542554
continue;
25552555
}
2556-
UpdatePSBTOutput(HidingSigningProvider(keys.get(), true, !bip32derivs), psbtx, i);
2556+
UpdatePSBTOutput(HidingSigningProvider(keys.get(), /*hide_secret=*/true, /*hide_origin=*/!bip32derivs), psbtx, i);
25572557
}
25582558

25592559
return TransactionError::OK;

src/wallet/spend.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ using interfaces::FoundBlock;
3030
namespace wallet {
3131
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
3232

33-
int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* provider, const CCoinControl* coin_control)
33+
int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* provider, bool can_grind_r, const CCoinControl* coin_control)
3434
{
3535
CMutableTransaction txn;
3636
txn.vin.push_back(CTxIn(outpoint));
37-
if (!provider || !DummySignInput(*provider, txn.vin[0], txout, coin_control)) {
37+
if (!provider || !DummySignInput(*provider, txn.vin[0], txout, can_grind_r, coin_control)) {
3838
return -1;
3939
}
4040
return GetVirtualTransactionInputSize(txn.vin[0]);
@@ -43,7 +43,7 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoin
4343
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, const CCoinControl* coin_control)
4444
{
4545
const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey);
46-
return CalculateMaximumSignedInputSize(txout, COutPoint(), provider.get(), coin_control);
46+
return CalculateMaximumSignedInputSize(txout, COutPoint(), provider.get(), wallet->CanGrindR(), coin_control);
4747
}
4848

4949
// txouts needs to be in the order of tx.vin
@@ -163,6 +163,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
163163
PreSelectedInputs result;
164164
std::vector<COutPoint> vPresetInputs;
165165
coin_control.ListSelected(vPresetInputs);
166+
const bool can_grind_r = wallet.CanGrindR();
166167
for (const COutPoint& outpoint : vPresetInputs) {
167168
int input_bytes = -1;
168169
CTxOut txout;
@@ -181,7 +182,7 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
181182
}
182183

183184
if (input_bytes == -1) {
184-
input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
185+
input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, can_grind_r, &coin_control);
185186
}
186187

187188
// If available, override calculated size with coin control specified size
@@ -214,6 +215,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
214215
const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH};
215216
const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
216217
const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true};
218+
const bool can_grind_r = wallet.CanGrindR();
217219

218220
std::set<uint256> trusted_parents;
219221
for (const auto& entry : wallet.mapWallet)
@@ -305,7 +307,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
305307

306308
std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(output.scriptPubKey);
307309

308-
int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl);
310+
int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), can_grind_r, coinControl);
309311
bool solvable = provider ? InferDescriptor(output.scriptPubKey, *provider)->IsSolvable() : false;
310312
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
311313

@@ -828,7 +830,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
828830
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
829831

830832
// Get size of spending the change output
831-
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, &wallet);
833+
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, &wallet, /*coin_control=*/nullptr);
832834
// If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
833835
// as lower-bound to allow BnB to do it's thing
834836
if (change_spend_size == -1) {

src/wallet/spend.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
namespace wallet {
1818
/** Get the marginal bytes if spending the specified output from this transaction.
1919
* Use CoinControl to determine whether to expect signature grinding when calculating the size of the input spend. */
20-
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, const CCoinControl* coin_control = nullptr);
21-
int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* pwallet, const CCoinControl* coin_control = nullptr);
20+
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, const CCoinControl* coin_control);
21+
int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* pwallet, bool can_grind_r, const CCoinControl* coin_control);
2222
struct TxSize {
2323
int64_t vsize{-1};
2424
int64_t weight{-1};

src/wallet/wallet.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,15 +1642,15 @@ void CWallet::InitWalletFlags(uint64_t flags)
16421642

16431643
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
16441644
// or a max-sized low-S signature (e.g. 72 bytes) depending on coin_control
1645-
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control)
1645+
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool can_grind_r, const CCoinControl* coin_control)
16461646
{
16471647
// Fill in dummy signatures for fee calculation.
16481648
const CScript& scriptPubKey = txout.scriptPubKey;
16491649
SignatureData sigdata;
16501650

16511651
// Use max sig if watch only inputs were used or if this particular input is an external input
16521652
// to ensure a sufficient fee is attained for the requested feerate.
1653-
const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(tx_in.prevout));
1653+
const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(tx_in.prevout) || !can_grind_r);
16541654
if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
16551655
return false;
16561656
}
@@ -1706,6 +1706,7 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
17061706
{
17071707
// Fill in dummy signatures for fee calculation.
17081708
int nIn = 0;
1709+
const bool can_grind_r = CanGrindR();
17091710
for (const auto& txout : txouts)
17101711
{
17111712
CTxIn& txin = txNew.vin[nIn];
@@ -1718,8 +1719,8 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
17181719
continue;
17191720
}
17201721
const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
1721-
if (!provider || !DummySignInput(*provider, txin, txout, coin_control)) {
1722-
if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, coin_control)) {
1722+
if (!provider || !DummySignInput(*provider, txin, txout, can_grind_r, coin_control)) {
1723+
if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, can_grind_r, coin_control)) {
17231724
return false;
17241725
}
17251726
}
@@ -4081,6 +4082,11 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
40814082
return true;
40824083
}
40834084

4085+
bool CWallet::CanGrindR() const
4086+
{
4087+
return !IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER);
4088+
}
4089+
40844090
bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, MigrationResult& res) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
40854091
{
40864092
AssertLockHeld(wallet.cs_wallet);

src/wallet/wallet.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
947947
//! Adds the ScriptPubKeyMans given in MigrationData to this wallet, removes LegacyScriptPubKeyMan,
948948
//! and where needed, moves tx and address book entries to watchonly_wallet or solvable_wallet
949949
bool ApplyMigrationData(MigrationData& data, bilingual_str& error) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
950+
951+
//! Whether the (external) signer performs R-value signature grinding
952+
bool CanGrindR() const;
950953
};
951954

952955
/**
@@ -1004,7 +1007,7 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
10041007
//! Remove wallet name from persistent configuration so it will not be loaded on startup.
10051008
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
10061009

1007-
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control = nullptr);
1010+
bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool can_grind_r, const CCoinControl* coin_control);
10081011

10091012
bool FillInputToWeight(CTxIn& txin, int64_t target_weight);
10101013

0 commit comments

Comments
 (0)