Skip to content
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

Fix cases of calls to FillPSBT errantly returning complete=true #30357

Merged
merged 2 commits into from
Jul 16, 2024

Conversation

willcl-ark
Copy link
Member

@willcl-ark willcl-ark commented Jun 28, 2024

Fixes: #30077

Fix cases of calls to FillPSBT returning complete=true when it's not
the case.

This can happen when some inputs have been signed but the transaction is
subsequently modified, e.g. in the context of PayJoins.

Also fixes a related bug where a finalized hex string is attempted to be
added during walletprocesspsbt but a CHECK_NONFATAL causes an abort.

@DrahtBot
Copy link
Contributor

DrahtBot commented Jun 28, 2024

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

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK ismaelsadeeq, pinheadmz, achow101
Concept ACK BrandonOdiwuor
Stale ACK spacebear21

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #28710 (Remove the legacy wallet and BDB dependency by achow101)

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.

@pinheadmz
Copy link
Member

concept ACK

Confirmed the bug on master, fixed by the patch in this PR.

I'm not sure responding to the user-provided "finalize" option in this way is entirely correct though. It seems to me that the "complete" value that we return could be inaccurate even before #28414 because we haven't called PSBTInputSignedAndVerified() yet. So that makes me wonder if we should be verifying in FillPSBT() here, instead of just checking that something is in the scriptsig / witness:

bitcoin/src/wallet/wallet.cpp

Lines 2226 to 2230 in fe70be5

// Complete if every input is now signed
complete = true;
for (const auto& input : psbtx.inputs) {
complete &= PSBTInputSigned(input);
}

...because as the issue demonstrates, the "complete" value could be misleading.

In other words, I think "complete" should be false already, before the "hex" was added to the rpc output.

@willcl-ark willcl-ark force-pushed the walletprocesspsbt-no-finalize branch 2 times, most recently from efa8138 to b6cea03 Compare July 1, 2024 19:47
@willcl-ark willcl-ark changed the title Permit opt-out of finalization during walletprocesspsbt Fix cases of calls to FillPSBT errantly returning complete=true Jul 1, 2024
Fix cases of calls to `FillPSBT` returning `complete=true` when it's not
the case.

This can happen when some inputs have been signed but the transaction is
subsequently modified, e.g. in the context of PayJoins.

Also fixes a related bug where a finalized hex string is attempted to be
added during `walletprocesspsbt` but a CHECK_NONFATAL causes an abort.

Reported in bitcoin#30077.
@willcl-ark willcl-ark force-pushed the walletprocesspsbt-no-finalize branch from b6cea03 to f57f68b Compare July 2, 2024 08:58
@willcl-ark
Copy link
Member Author

Rebased to fix CI

@DrahtBot DrahtBot removed the CI failed label Jul 2, 2024
Copy link
Member

@pinheadmz pinheadmz left a comment

Choose a reason for hiding this comment

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

ACK f57f68b

Reviewed code and confirmed test fails without patch. Approach is great, one question below about how much farther to take this approach...

Show Signature
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

ACK f57f68ba95fc8778fe2dc755da1e631fe60592c4
-----BEGIN PGP SIGNATURE-----

iQIzBAEBCAAdFiEE5hdzzW4BBA4vG9eM5+KYS2KJyToFAmaECVgACgkQ5+KYS2KJ
yTq5vw//QNiwjThHf65qtM+57G6YDPWQ3zVu2s278T0aZU5ND62Hu4A9/DHqDBSQ
36A4klRON1ddIfFX91tE3GrwT4PZ51vK3uhX0xJYkZ66xkhObVZz2lCifMs+A2ng
ldEoEBT86d17fyuQLle8/VNolqXqWx9Dw5RZY3WclnO0Hex2l9xiWepel4sFzAM8
P4YUgsw6ZaEbRUjh67tZ3kLlmz5l5yBNV5NaO7rbsPeBiIqizRbGl7CQlhlxgM4g
ZF4PGUd9rk0sSnWWMHUcVA+Uskuw64bDH0keJBLUYeKpNLfRUZ5KTRSJilurayY5
M2B7veORZ4MWjGHjycbpV+hdpjpQzX5z1cyCxGqYN4gC8P7kpFVBrB6sMUvOLoku
lGTC+SKE+sxjRmgYcE1QpPMz7uYs8KTgkTHpzlbyj61oEL1SylXDtOlc7nRN2I0A
XEnV8kK/2VKF3V45dTRPUwFm+2qX1NnCEyJ53x2srg9+U3px9UxfUMAuwTmZYyOV
E/9XCDouqE63CqCzqM683lmdL+sCsfA5IHi9sqzVRP2cY8s1NOkj7QdXQO0Z8V07
ycsGpj8nI8GAq/b647aYOU9O+Ni2PGq2wdkpP8ciD0Iafp7S9MkpCjHx9J+p4YPE
TQVElxmGYgx5zIPFrdrBehZCJGX/YDqj8Zd9Ra0QYwVzYg9aqow=
=KJef
-----END PGP SIGNATURE-----

pinheadmz's public key is on keybase

Comment on lines +90 to +91
signed_psbt_incomplete = wallet.walletprocesspsbt(signed_psbt_obj.to_base64(), finalize=False)
assert signed_psbt_incomplete["complete"] is False
Copy link
Member

Choose a reason for hiding this comment

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

This makes me wonder if finalize=True should result in "complete": True. It's another case of using PSBTInputSigned() instead of PSBTInputSignedAndVerified(). We could update this code below to automatically replace / update invalid signatures.

bitcoin/src/wallet/wallet.cpp

Lines 2181 to 2194 in d2c8d16

std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs, size_t * n_signed, bool finalize) const
{
if (n_signed) {
*n_signed = 0;
}
LOCK(cs_wallet);
// Get all of the previous transactions
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) {
continue;
}

Comment on lines 84 to 87
signed_psbt_obj = PSBT.from_base64(signed_psbt)
substitute_addr = wallet.getnewaddress(address_type="bech32m")
raw = wallet.createrawtransaction([{"txid": utxos[0]["txid"], "vout": utxos[0]["vout"]}], [{substitute_addr: 0.9999}])
signed_psbt_obj.g.map[PSBT_GLOBAL_UNSIGNED_TX] = bytes.fromhex(raw)
Copy link
Member

Choose a reason for hiding this comment

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

There might be a simpler way to malleate the PSBT. You could at least skip the getnewaddress call and just hard-code something, or change one of the output amounts. Maybe only if you retouch.

@spacebear21
Copy link

Thanks @willcl-ark for picking this up! Confirmed this fixes the CHECK_NONFATAL abort when a witness signature is invalid.

tACK f57f68b

self.log.info("Check that PSBT is correctly marked as incomplete after invalid modification")
node = self.nodes[2]
wallet = node.get_wallet_rpc(self.default_wallet_name)
address = wallet.getnewaddress(address_type="bech32m")
Copy link
Member

Choose a reason for hiding this comment

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

In b6cea03 "test: add test for modififed walletprocesspsbt calls"

Removing address_type here and below would allow this test to run on legacy wallets so it does not need the if self.options.descriptors below (which would remove the conflict with #28710 and save me a rebase).

Copy link
Member Author

Choose a reason for hiding this comment

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

Done in 7e36dca

This test checks that we can successfully process PSBTs and opt out of
finalization.

Previously trying to call `walletprocesspsbt` would attempt to
auto-finalize (as a convenience), and would not permit opt-out of
finalization, instead aborting via `CHECK_NONFATAL`.
@willcl-ark willcl-ark force-pushed the walletprocesspsbt-no-finalize branch from f57f68b to 7e36dca Compare July 3, 2024 08:31
@fanquake fanquake added this to the 28.0 milestone Jul 3, 2024
Comment on lines +79 to +80
utxos = wallet.listunspent(addresses=[address])
psbt = wallet.createpsbt([{"txid": utxos[0]["txid"], "vout": utxos[0]["vout"]}], [{wallet.getnewaddress(): 0.9999}])
Copy link
Member

Choose a reason for hiding this comment

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

nano nit:

Suggested change
utxos = wallet.listunspent(addresses=[address])
psbt = wallet.createpsbt([{"txid": utxos[0]["txid"], "vout": utxos[0]["vout"]}], [{wallet.getnewaddress(): 0.9999}])
psbt = wallet.createpsbt(utxos, [{wallet.getnewaddress(): 0.9999}])

Comment on lines +84 to +90
signed_psbt_obj = PSBT.from_base64(signed_psbt)
substitute_addr = wallet.getnewaddress()
raw = wallet.createrawtransaction([{"txid": utxos[0]["txid"], "vout": utxos[0]["vout"]}], [{substitute_addr: 0.9999}])
signed_psbt_obj.g.map[PSBT_GLOBAL_UNSIGNED_TX] = bytes.fromhex(raw)

# Check that the walletprocesspsbt call succeeds but also recognizes that the transaction is not complete
signed_psbt_incomplete = wallet.walletprocesspsbt(signed_psbt_obj.to_base64(), finalize=False)
Copy link
Member

Choose a reason for hiding this comment

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

Seems that the PSBT 'outputs' field retains the previous output derivation path after replacement. walletprocesspsbt appends the new information without removing/updating the previous one. See furszy@698e090.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @furszy, this indeed feels unclean to me.

I made a few changes in another test branch and I wonder if they make sense to you: master...willcl-ark:bitcoin:walletprocesspsbt-no-finalize-fixups

I took inspiration from furszy@698e090 (thanks) but it seems that I can probably just clear the entire output map as done in L94 and then update the PSBT_GLOBAL_UNSIGNED_TX in the global map before calling walletprocesspsbt again.

Copy link
Member

Choose a reason for hiding this comment

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

I made a few changes in another test branch and I wonder if they make sense to you: master...willcl-ark:bitcoin:walletprocesspsbt-no-finalize-fixups

The question here is whether walletprocesspsbt should update the existing PSBT 'outputs' information (similar to how it updates the transaction inputs) or should only append data to it as it is currently doing. In the current test form, the first entry of the PSBT 'outputs' field ends up with two BIP32 derivation paths that cannot coexist: they share the same master key fingerprint and are under the same derivation path: the first one is m/.../15 while the other one is m/.../16.

Clearing the map on the test side hides this topic under the carpet (which is also a possibility if you want to leave it for a follow-up too).

Copy link
Member

Choose a reason for hiding this comment

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

All of our PSBT updating operations should never remove any data from the PSBT. If the user decides to do something that invalidates a field in the PSBT, it's their duty to fix it themselves.

Copy link
Contributor

@BrandonOdiwuor BrandonOdiwuor 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

Copy link
Member

@ismaelsadeeq ismaelsadeeq left a comment

Choose a reason for hiding this comment

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

Tested ACK 7e36dca

I've verified that this PR fixes the issue #30077, complete=false is returned instead of throwing an error.

Just a single test question and a non-blocking nit.

signed_psbt_obj.g.map[PSBT_GLOBAL_UNSIGNED_TX] = bytes.fromhex(raw)

# Check that the walletprocesspsbt call succeeds but also recognizes that the transaction is not complete
signed_psbt_incomplete = wallet.walletprocesspsbt(signed_psbt_obj.to_base64(), finalize=False)
Copy link
Member

Choose a reason for hiding this comment

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

Why pass finalize=false? the test still passes without it.


# Check that the walletprocesspsbt call succeeds but also recognizes that the transaction is not complete
signed_psbt_incomplete = wallet.walletprocesspsbt(signed_psbt_obj.to_base64(), finalize=False)
assert signed_psbt_incomplete["complete"] is False
Copy link
Member

Choose a reason for hiding this comment

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

nit

Suggested change
assert signed_psbt_incomplete["complete"] is False
assert_equal(signed_psbt_incomplete["complete"], False)

Copy link
Member

@pinheadmz pinheadmz left a comment

Choose a reason for hiding this comment

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

re-ACK 7e36dca

Changes since last ACK are minimal, slight simplification in tests to enable more legacy compatability

Show Signature
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

ACK 7e36dca657c66bc70b04d5b850e5a335aecfb902
-----BEGIN PGP SIGNATURE-----

iQIzBAEBCAAdFiEE5hdzzW4BBA4vG9eM5+KYS2KJyToFAmaRgqoACgkQ5+KYS2KJ
yTq5NA/+NL1fOaCfl+P9yDIsXebxam/8Adf6iH5WUBt2TvUju06+305KL1dwvO6O
n0j8wmAeaJQe9tevuSTFYopxRaH+7uVQmrMDxKucURgBZ5I/VdBWk8v3+CPUsnKQ
v/hyt4RPMsL12rcti936UqKi77nfRc6E+7zsDOtRU0Rf6ME222lCaWUpqAjm4vPm
3nN18eXw6Az9rKHwG3KFzXxMy/bthlI6bsRtvbUqtFc6PkZ7xR7f79EauawCkJWS
vCr4RXVcrC+1FMB8MHMcbLiNTmNjGApg48PAYnaoJsolpGQoRaHRNwKeRo2w0Uvw
tk3uXeVNfwxRbYwzBI6pZ/JYTSa3u31qsVGqx3mVVxm8y1F/sA+fwsRB+AqToDaj
Kzf4P8FLkyYT1t83i1n5yRKawHrQTVcsMVhxsN/+4nqBbvdGba6E5SpXG3fBJ3sI
L/fJocldQmO9maY+I9CgjJqmM9GauMvhjSq+g0PnWE9pRUnt1n0bFy7DwdonzRFK
bXtFx2xcWxtrHSAvLZt6oxTXTqwfh5XiFMHwG93ULySpzOJTHB+lTED1TKf4gwhk
QcaVW4Oi0lQ8QE6rtq+o0nx0FFz+lA9RoT2MFWQU2L+HFGqokwI9PQTVWxzm9Vjd
ZWOF04PI0QvCz0DITFHV0HTX1YsyQobWWWXRxwttqy4GDUi0PPw=
=DAZD
-----END PGP SIGNATURE-----

pinheadmz's public key is on keybase

@achow101
Copy link
Member

achow101 commented Jul 16, 2024

ACK 7e36dca

@achow101 achow101 merged commit 6f9db1e into bitcoin:master Jul 16, 2024
15 of 16 checks passed
fanquake pushed a commit to fanquake/bitcoin that referenced this pull request Jul 17, 2024
Fix cases of calls to `FillPSBT` returning `complete=true` when it's not
the case.

This can happen when some inputs have been signed but the transaction is
subsequently modified, e.g. in the context of PayJoins.

Also fixes a related bug where a finalized hex string is attempted to be
added during `walletprocesspsbt` but a CHECK_NONFATAL causes an abort.

Reported in bitcoin#30077.

Github-Pull: bitcoin#30357
Rebased-From: 39cea21
fanquake pushed a commit to fanquake/bitcoin that referenced this pull request Jul 17, 2024
This test checks that we can successfully process PSBTs and opt out of
finalization.

Previously trying to call `walletprocesspsbt` would attempt to
auto-finalize (as a convenience), and would not permit opt-out of
finalization, instead aborting via `CHECK_NONFATAL`.

Github-Pull: bitcoin#30357
Rebased-From: 7e36dca
fanquake pushed a commit to fanquake/bitcoin that referenced this pull request Jul 17, 2024
Fix cases of calls to `FillPSBT` returning `complete=true` when it's not
the case.

This can happen when some inputs have been signed but the transaction is
subsequently modified, e.g. in the context of PayJoins.

Also fixes a related bug where a finalized hex string is attempted to be
added during `walletprocesspsbt` but a CHECK_NONFATAL causes an abort.

Reported in bitcoin#30077.

Github-Pull: bitcoin#30357
Rebased-From: 39cea21
fanquake pushed a commit to fanquake/bitcoin that referenced this pull request Jul 17, 2024
This test checks that we can successfully process PSBTs and opt out of
finalization.

Previously trying to call `walletprocesspsbt` would attempt to
auto-finalize (as a convenience), and would not permit opt-out of
finalization, instead aborting via `CHECK_NONFATAL`.

Github-Pull: bitcoin#30357
Rebased-From: 7e36dca
@fanquake fanquake mentioned this pull request Jul 17, 2024
@fanquake
Copy link
Member

Backported to 27.x in #30467.

fanquake added a commit that referenced this pull request Jul 24, 2024
4f23c86 [WIP] doc: update release notes for 27.x (fanquake)
54bb9b0 test: add test for modififed walletprocesspsbt calls (willcl-ark)
f22b9ca wallet: fix FillPSBT errantly showing as complete (willcl-ark)
05192ba init: change shutdown order of load block thread and scheduler (Martin Zumsande)
ab42206 Reapply "test: p2p: check that connecting to ourself leads to disconnect" (Sebastian Falbesoner)
064f214 net: prevent sending messages in `NetEventsInterface::InitializeNode` (Sebastian Falbesoner)
0933cf5 net: fix race condition in self-connect detection (Sebastian Falbesoner)
fa90989 psbt: Check non witness utxo outpoint early (Ava Chow)

Pull request description:

  Backports:
  * #29855
  * #30357
  * #30394 (modified test commit)
  * #30435

ACKs for top commit:
  stickies-v:
    ACK 4f23c86
  willcl-ark:
    ACK 4f23c86

Tree-SHA512: 5c26445f0855f9d14890369ce19873b0686804eeb659e7d6da36a6f404f64d019436e1e6471579eaa60a96ebf8f64311883b4aef9d0ed528a95bd610c101c079
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Aug 1, 2024
Fix cases of calls to `FillPSBT` returning `complete=true` when it's not
the case.

This can happen when some inputs have been signed but the transaction is
subsequently modified, e.g. in the context of PayJoins.

Also fixes a related bug where a finalized hex string is attempted to be
added during `walletprocesspsbt` but a CHECK_NONFATAL causes an abort.

Reported in bitcoin#30077.

Github-Pull: bitcoin#30357
Rebased-From: 39cea21
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Aug 1, 2024
This test checks that we can successfully process PSBTs and opt out of
finalization.

Previously trying to call `walletprocesspsbt` would attempt to
auto-finalize (as a convenience), and would not permit opt-out of
finalization, instead aborting via `CHECK_NONFATAL`.

Github-Pull: bitcoin#30357
Rebased-From: 7e36dca
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
9 participants