-
Notifications
You must be signed in to change notification settings - Fork 35.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wallet: skip R-value signature grinding for external signers #26032
wallet: skip R-value signature grinding for external signers #26032
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
Added a release note. |
9558c8a
to
a58038a
Compare
A functional test would be nice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would make sense to move grind
to be an attribute of (or at least method of) the SigningProvider?
src/psbt.cpp
Outdated
@@ -320,7 +320,7 @@ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& | |||
return txdata; | |||
} | |||
|
|||
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata, bool finalize) | |||
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata, bool finalize, bool grind) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: grind
is a bit unclear, maybe require_optimal_signature_size
or !use_max_sig
(like in wallet_tests)?
src/wallet/scriptpubkeyman.cpp
Outdated
@@ -618,7 +618,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con | |||
return SigningResult::SIGNING_FAILED; | |||
} | |||
|
|||
TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const | |||
TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize, bool grind) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
grind
gets ignored here. What if a watch-only input is used, and the signer for it can't grind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK, but I am a bit confused about the approach so I've left a question below.
src/psbt.cpp
Outdated
@@ -361,7 +361,7 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& | |||
sigdata.witness = false; | |||
bool sig_complete; | |||
if (txdata == nullptr) { | |||
sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); | |||
sig_complete = ProduceSignature(provider, grind ? DUMMY_SIGNATURE_CREATOR : DUMMY_MAXIMUM_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like the dummy signature produced here isn't used for fee estimation, but for figuring out what signatures that the psbt is missing (for use when analyzing a psbt). It looks like this behavior was introduced in cb40b3a.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@achow101 I could use some hints here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Size estimation occurs in CreateTransaction
, inside of SelectCoins
and CalculateMaximumSignedInputSize
. FillPSBT
and the functions it calls shouldn't be touched.
Indeed, but I'm not sure how. Can our test suite produce ungrinded signatures? |
Worst case, use a hardcoded descriptor and signatures? |
@luke-jr in that case the entire regtest chain needs to be deterministic, otherwise you're spending non-existing coins. But IIRC it is, so I'll take another look at that. Does seem a bit brittle. |
I changed the approach. Since it piggybacks on the logic for external inputs I think a functional test is less necessary? |
bb5b170
to
b133ab9
Compare
src/wallet/coincontrol.h
Outdated
//! Wallet inputs are signed by an external signer | ||
bool m_external_signer = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will be*
But I don't think this is a good name for the flag. It's quite possible external signers might support grinding, and will want a way to optimise fees too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could call it m_can_grind_r
and default to true
? And then for external signers we set it to false
until we have a way (e.g. via HWI) to communicate this capability. That said, I don't know if anyone will implement it, since Taproot doesn't have this problem.
src/wallet/spend.cpp
Outdated
TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), bool(res), | ||
res ? res->fee : 0, res ? res->change_pos : 0); | ||
if (!res) return res; | ||
const auto& txr_ungrouped = *res; | ||
// try with avoidpartialspends unless it's enabled already | ||
if (txr_ungrouped.fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { | ||
if (txr_ungrouped.fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !tmp_cc.m_avoid_partial_spends) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to change this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, but accessing both the original coin_control
and tmp_cc
seems like asking for future regressions.
Also, please don't rebase when there's no reason to. (If you could rebase back, it'd be nice.) |
Given how much refactoring is (still) going on in the wallet, not rebasing increases the risk of a silent merge conflict. Although in this case back-porting might be very useful, so I'll see if I can move it back to something a bit older. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding the m_external_signer
flag to coin control, what about passing wallet.IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)
to the DummySignInput
function? (or pass the wallet ref and call IsWalletFlagSet
internally).
I think that we should keep the coin control object as a container for user modifiable parameters. And not start using it to pass wallet internal flags across functions.
@furszy there's three call sites to I could expand The easier way is to add an |
yeah,
Would look like this furszy@dccb120. Not that many changes. Plus, could also include the unused |
ACK b133ab9
I think that would be a layer violation. The |
I'll leave this PR as-is then. We can always refactor things. |
Concept ACK. This PR is a clearly an improvement from users' perspective. Approach wise, I don't like that I reviewed both this PR and the alternative approach and I like the second option better. You can make it even simpler like this S3RK/bitcoin@8f11705 |
I considered switching to @S3RK's approach of adding extra argument to The downside of this is approach is that I had to sprinkle So I'm keeping the original commit for now. |
What if we encapsulate the logic in The ability to grind signatures is a characteristic of a wallet IIUC. So that should be pretty future proof. |
b133ab9
to
302f07e
Compare
@S3RK done! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 302f07e
Looking quite straightforward now.
src/wallet/spend.cpp
Outdated
@@ -305,7 +305,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(), wallet.CanGrindR(), coinControl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiny nit:
could cache this result into a variable outside of the loop (after the only_safe
variable)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I introduced a const bool
in both all three places where it's used in a loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though perhaps it's overkill, because IsWalletFlagSet
is just m_wallet_flags & flag
and not a database lookup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I think that the only place that worth is inside AvailableCoins
. And not only because of the overhead that introduces (it's one "AND" operation on an atomic variable per wallet UTXO), I was more thinking about the code structure and what we should do if we want to decrease the cs_wallet
lock contention there.
still, this is a tiny nit from a person that is thinking about future stuff.
540ec59
to
98a4dad
Compare
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. This commit also drops the nullptr default for CCoinControl arguments for functions that it touches. This is because having a boolean argument right next to an optional pointer is error prone. Co-Authored-By: S3RK <1466284+S3RK@users.noreply.github.com>
98a4dad
to
807de2c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 807de2c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 807de2c
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this comment can be updated
// 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. | |
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added to a followup PR #27180.
ACK 807de2c Thanks your patience and addressing the feedback. |
ACK 807de2c |
…rnal 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 bitcoin#26030 ACKs for top commit: S3RK: ACK 807de2c achow101: ACK 807de2c furszy: ACK 807de2c ishaanam: utACK 807de2c Tree-SHA512: 64f626a3030ef0ab1e43af86d8fba113151512561baf425e6e5182af53df3a64fa9e85c7f67bf4ed15b5ad6e5d5afc7fbba8b6e1f3bad388e48db51cb9446074
6fc5f4f doc: DummySignInput mention external signer (Sjors Provoost) Pull request description: Followups for #26032. So far nothing major. ACKs for top commit: ishaanam: ACK 6fc5f4f S3RK: ACK 6fc5f4f Tree-SHA512: e27edde9853487fe3eef8213f991aae3724f318bbbe0b11da23759879adaf9a31771e6ea0c30baaebca149032780b89b32aa540ff456ca3d5ec6adb0371749c6
6fc5f4f doc: DummySignInput mention external signer (Sjors Provoost) Pull request description: Followups for bitcoin#26032. So far nothing major. ACKs for top commit: ishaanam: ACK 6fc5f4f S3RK: ACK 6fc5f4f Tree-SHA512: e27edde9853487fe3eef8213f991aae3724f318bbbe0b11da23759879adaf9a31771e6ea0c30baaebca149032780b89b32aa540ff456ca3d5ec6adb0371749c6
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:
On master, launch with
-signet
and create an external signer wallet using e.g. a Trezor and HWI, see guide (with the GUI it should "just work" once you have the HWI path configured).Create a few addresses and fund them from the faucet: https://signet.bc-2.jp/ (wait for confirmation)
Create another address, and now send the entire wallet to it, set the fee to 1 sat/byte
Most likely this transaction never gets broadcast and you won't see it on the signet explorer
With this PR, try again.
Check the explorer and inspect the transaction. Each input witness starts with either
30440220
(R has 32 bytes) or30440221
(R has 33 bytes). See this explainer for DER encoding.Fixes #26030