diff --git a/src/core_io.cpp b/src/core_io.cpp index 3650d70810a..cbaf2417123 100644 --- a/src/core_io.cpp +++ b/src/core_io.cpp @@ -338,9 +338,13 @@ const std::map mapSigHashTypes = { {static_cast(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY), std::string("SINGLE|ANYONECANPAY")}, }; -std::string SighashToStr(unsigned char sighash_type) +std::string SighashToStr(int32_t sighash_type) { - const auto& it = mapSigHashTypes.find(sighash_type); + // Signatures encode the sighash type in a single byte, but the PSBT field + // for it is a 32 bit unsigned integer in BIP 174 (signed in PSBTInput) + if (sighash_type < 0 || sighash_type > 0xff) return ""; + const uint8_t sighash_byte(sighash_type); + const auto& it = mapSigHashTypes.find(sighash_byte); if (it == mapSigHashTypes.end()) return ""; return it->second; } diff --git a/src/core_io.h b/src/core_io.h index 904f5a8643b..d78706216c2 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -42,7 +43,7 @@ bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header); UniValue ValueFromAmount(CAmount amount); std::string FormatScript(const CScript& script); std::string EncodeHexTx(const CTransaction& tx); -std::string SighashToStr(unsigned char sighash_type); +std::string SighashToStr(int32_t sighash_type); void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex = true, bool include_address = false, const SigningProvider* provider = nullptr); void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex = true, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS, std::function is_change_func = {}); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index e9eae656b51..d64384d4ec9 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1224,7 +1224,7 @@ static RPCMethod decodepsbt() // Sighash if (input.sighash_type != std::nullopt) { - in.pushKV("sighash", SighashToStr((unsigned char)*input.sighash_type)); + in.pushKV("sighash", SighashToStr(*input.sighash_type)); } // Redeem script and witness script diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 23273d54b56..84c11278b92 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -557,6 +557,14 @@ class PSBTTest(BitcoinTestFramework): wallet.unloadwallet() + def test_decodepsbt_long_sighash_type(self): + self.log.info("Test that decodepsbt rejects invalid trailing bytes in the sighash type field") + node = self.nodes[0] + psbt = PSBT.from_base64(node.createpsbt([{"txid": "00" * 32, "vout": 0}], [{"data": "00"}])) + # The first byte of this sighash type is ALL, but the type itself is not + psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (0x101).to_bytes(4, "little") + assert_equal(node.decodepsbt(psbt.to_base64())["inputs"][0]["sighash"], "") + def assert_change_type(self, psbtx, expected_type): """Assert that the given PSBT has a change output with the given type.""" @@ -1619,6 +1627,7 @@ class PSBTTest(BitcoinTestFramework): if not self.options.usecli: self.test_sighash_mismatch() self.test_sighash_adding() + self.test_decodepsbt_long_sighash_type() self.test_psbt_named_parameter_handling() self.test_psbt_roundtrip() self.test_psbt_version()