Skip to content

Defer transaction signing until user clicks Send#915

Open
151henry151 wants to merge 1 commit intobitcoin-core:masterfrom
151henry151:fix-30070-defer-signing
Open

Defer transaction signing until user clicks Send#915
151henry151 wants to merge 1 commit intobitcoin-core:masterfrom
151henry151:fix-30070-defer-signing

Conversation

@151henry151
Copy link
Contributor

@151henry151 151henry151 commented Nov 22, 2025

Fixes #30070

When creating an unsigned PSBT from the GUI, the transaction was already signed during preparation, causing legacy inputs to have non-empty scriptSig fields. The PSBT parser then rejects them.

This defers signing until the user clicks "Send" instead of signing during preparation. Fee calculation still works since transactions can be created without signing.

Follows the approach suggested by @achow101 in the issue comments.

@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 22, 2025

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK achow101

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

Conflicts

Reviewers, 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.

LLM Linter (✨ experimental)

Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

  • tr("%1 kvB (unsigned)", "PSBT transaction creation").arg((double)m_current_transaction->getTransactionSize() / 1000, 0, 'g', 3) in src/qt/sendcoinsdialog.cpp

2026-03-16 01:01:50

@hebasto hebasto changed the title qt: Defer transaction signing until user clicks Send Defer transaction signing until user clicks Send Nov 22, 2025
@151henry151
Copy link
Contributor Author

It looks like the one failing check is a CI issue with disk space too low. I'm not sure how to address that. I think the code of the PR is correct.

@151henry151
Copy link
Contributor Author

@achow101 would you like to check this out and let me know if it looks OK?

@maflcko maflcko closed this Dec 17, 2025
@maflcko maflcko reopened this Dec 17, 2025
@151henry151
Copy link
Contributor Author

Should I push an empty commit to trigger a re-run of the CI here, or is there some other action needed?

@hebasto
Copy link
Member

hebasto commented Jan 19, 2026

Should I push an empty commit to trigger a re-run of the CI here, or is there some other action needed?

No action needed.

@hebasto
Copy link
Member

hebasto commented Jan 19, 2026

cc @achow101 @furszy

@hebasto hebasto added Wallet and removed CI failed labels Jan 19, 2026
Copy link
Member

@achow101 achow101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

@151henry151
Copy link
Contributor Author

For the rebase, I kept master's new createTransaction usage and error flow, wired in the defer-signing logic (sign parameter), and re-added the AmountExceedsBalance check after a successful result. I initially missed updating that to AmountExceedsBalance, but I’ve corrected it. If anyone wants to check this out and let me know if it looks correct, I'd appreciate any feedback. Thanks

@151henry151
Copy link
Contributor Author

Would anyone like to take another look at this? Any review or feedback welcomed

Copy link
Contributor

@johnny9 johnny9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double check this works with locked encrypted wallets

Copy link
Contributor

@johnny9 johnny9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to move the UnlockContext when you sign the transaction otherwise it fails to send now.

Screen.Recording.2026-03-04.at.11.32.40.AM.mov

@151henry151
Copy link
Contributor Author

Thanks @johnny9 , I'll take a closer look this evening

@151henry151 151henry151 force-pushed the fix-30070-defer-signing branch from 9b65413 to 39a5fed Compare March 5, 2026 22:05
Copy link
Contributor

@johnny9 johnny9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving the UnlockContext in the "Send" click flow resolved the previous issue. I think the previous UnlockContext is no longer needed.

I also see that the send confirmation dialog isn't as accurate as it was previously so should adjust what the dialog says or expose the correct fee estimated size through the model.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little different now that the current transaction isn't signed yet so the kvB is different now than before. Can either change the dialog to make this clear or adjust this size to be the same as the estimated size the fee calculation uses.

Copy link
Contributor

@johnny9 johnny9 Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

For this example vsize should equal the fee (208 kvB and 208 sat fee). Without your changes they were same and now the size of the transaction doesnt include the signed portion so it isn't matching the fee.

This fixes issue #30070 where creating unsigned PSBTs from the GUI
would fail because the transaction was already signed during preparation,
causing legacy inputs to have non-empty scriptSig fields.

The fix defers signing until the user explicitly clicks 'Send', allowing
truly unsigned PSBTs to be created while still supporting fee calculation.
@151henry151 151henry151 force-pushed the fix-30070-defer-signing branch from 39a5fed to 6a999c2 Compare March 16, 2026 01:01
@151henry151
Copy link
Contributor Author

@johnny9 I think I've fixed it correctly; I tested locally (built the gui and tested manually) and it seems like the issues are resolved. Could you test it again to confirm, and take a look at the changes to see if there are any mistakes or problems you can see?

@151henry151 151henry151 requested a review from johnny9 March 16, 2026 15:59
Copy link
Contributor

@johnny9 johnny9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think I was wrong and we need both UnlockContexts. Sorry to flp-flop on that. Also take a look at achow's comment again. Seems like you didnt remove the parameter he mentioned. Other than those things I think this looks good now

WalletModel::UnlockContext ctx(model->requestUnlock());
if(!ctx.isValid())
{
// Unlock wallet was cancelled
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was wrong about removing this. I think you will need unlock here too to be sure you can get a change address.

@@ -95,7 +95,7 @@ class WalletModel : public QObject
};

// prepare transaction for getting txfee before sending coins
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl& coinControl);
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl& coinControl, bool sign = false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sign parameter can be removed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Load PSBT error: Unable to decode PSBT

6 participants