From 14e35e675c576c770641160e982713dda4032bd8 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Wed, 5 Mar 2025 11:35:04 -0800 Subject: [PATCH 01/11] psbt: Require ECDSA signatures to be validly encoded --- src/psbt.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/psbt.h b/src/psbt.h index 6d49864b3cd..2f72f02f84b 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -429,6 +429,11 @@ struct PSBTInput std::vector sig; s >> sig; + // Check that the signature is validly encoded + if (sig.empty() || !CheckSignatureEncoding(sig, SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_STRICTENC, nullptr)) { + throw std::ios_base::failure("Signature is not a valid encoding"); + } + // Add to list partial_sigs.emplace(pubkey.GetID(), SigPair(pubkey, std::move(sig))); break; From 2a721dcfae44cb0fef6825e773e5895cb87a91a5 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Wed, 8 Jan 2025 15:49:15 -0500 Subject: [PATCH 02/11] tests: Test PSBT sighash type mismatch --- test/functional/rpc_psbt.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 8042bdf0715..f9762412338 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -27,13 +27,14 @@ from test_framework.psbt import ( PSBT_GLOBAL_UNSIGNED_TX, PSBT_IN_RIPEMD160, PSBT_IN_SHA256, + PSBT_IN_SIGHASH_TYPE, PSBT_IN_HASH160, PSBT_IN_HASH256, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_WITNESS_UTXO, PSBT_OUT_TAP_TREE, ) -from test_framework.script import CScript, OP_TRUE +from test_framework.script import CScript, OP_TRUE, SIGHASH_ALL, SIGHASH_ANYONECANPAY from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( @@ -201,6 +202,34 @@ class PSBTTest(BitcoinTestFramework): wallet.unloadwallet() + def test_sighash_mismatch(self): + self.log.info("Test sighash type mismatches") + self.nodes[0].createwallet("sighash_mismatch") + wallet = self.nodes[0].get_wallet_rpc("sighash_mismatch") + def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + addr = wallet.getnewaddress(address_type="bech32") + def_wallet.sendtoaddress(addr, 5) + self.generate(self.nodes[0], 6) + + # Make a PSBT + psbt = wallet.walletcreatefundedpsbt([], [{def_wallet.getnewaddress(): 1}])["psbt"] + + # Modify the PSBT and insert a sighash field for ALL|ANYONECANPAY on input 0 + mod_psbt = PSBT.from_base64(psbt) + mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL | SIGHASH_ANYONECANPAY).to_bytes(4, byteorder="little") + psbt = mod_psbt.to_base64() + + # Mismatching sighash type fails, including when no type is specified + for sighash in ["DEFAULT", "ALL", "NONE", "SINGLE", "NONE|ANYONECANPAY", "SINGLE|ANYONECANPAY", None]: + assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, sighash) + + # Matching sighash type succeeds + proc = wallet.walletprocesspsbt(psbt, True, "ALL|ANYONECANPAY") + assert_equal(proc["complete"], True) + + wallet.unloadwallet() + def assert_change_type(self, psbtx, expected_type): """Assert that the given PSBT has a change output with the given type.""" @@ -1051,6 +1080,7 @@ class PSBTTest(BitcoinTestFramework): self.log.info("Test descriptorprocesspsbt raises if an invalid sighashtype is passed") assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], sighashtype="all") + self.test_sighash_mismatch() if __name__ == '__main__': PSBTTest(__file__).main() From 5332407ccbe0c2700fc65c1db805b637ee187e37 Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Wed, 8 Jan 2025 14:37:38 -0500 Subject: [PATCH 03/11] psbt: Return PSBTError from SignPSBTInput SignPSBTInput will need to report the specific things that caused an error to callers, so change it to return a PSBTError. Additionally some callers will now check the return value and report an error to the user. Currently, this should not change any behavior as the things that SignPBSTInput will error on are all first checked by its callers. --- src/common/messages.cpp | 4 ++++ src/common/types.h | 2 ++ src/node/psbt.cpp | 4 ++-- src/psbt.cpp | 19 +++++++++++-------- src/psbt.h | 5 ++++- src/rpc/rawtransaction.cpp | 3 ++- src/wallet/scriptpubkeyman.cpp | 10 ++++++++-- 7 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/common/messages.cpp b/src/common/messages.cpp index 5fe3e9e4d86..fc0b4a8861f 100644 --- a/src/common/messages.cpp +++ b/src/common/messages.cpp @@ -116,6 +116,10 @@ bilingual_str PSBTErrorString(PSBTError err) return Untranslated("External signer failed to sign"); case PSBTError::UNSUPPORTED: return Untranslated("Signer does not support PSBT"); + case PSBTError::INCOMPLETE: + return Untranslated("Input needs additional signatures or other data"); + case PSBTError::OK: + return Untranslated("No errors"); // no default case, so the compiler can warn about missing cases } assert(false); diff --git a/src/common/types.h b/src/common/types.h index 0d9cb67ce94..fb9ee4bf31b 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -20,6 +20,8 @@ enum class PSBTError { EXTERNAL_SIGNER_NOT_FOUND, EXTERNAL_SIGNER_FAILED, UNSUPPORTED, + INCOMPLETE, + OK, }; } // namespace common diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index 51e252bffca..079c7e2d4e2 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -64,7 +64,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Figure out what is missing SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata); + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata) == PSBTError::OK; // Things are missing if (!complete) { @@ -124,7 +124,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) PSBTInput& input = psbtx.inputs[i]; Coin newcoin; - if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) { + if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) != PSBTError::OK || !psbtx.GetInputUTXO(newcoin.out, i)) { success = false; break; } else { diff --git a/src/psbt.cpp b/src/psbt.cpp index 19d855e4c78..d0051c3143c 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -4,12 +4,15 @@ #include +#include #include #include #include