diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index b866fa484b..ba329fc9ef 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -171,6 +171,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "walletprocesspsbt", 3, "bip32derivs" }, { "walletprocesspsbt", 4, "finalize" }, { "descriptorprocesspsbt", 1, "descriptors"}, + { "descriptorprocesspsbt", 2, "options" }, + { "descriptorprocesspsbt", 2, "bip32derivs" }, + { "descriptorprocesspsbt", 2, "finalize" }, { "descriptorprocesspsbt", 3, "bip32derivs" }, { "descriptorprocesspsbt", 4, "finalize" }, { "createpsbt", 0, "inputs" }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 21bc0e52f1..d7f0b8fd42 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1937,16 +1937,23 @@ RPCHelpMan descriptorprocesspsbt() {"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"}, }}, }}, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" - " \"DEFAULT\"\n" - " \"ALL\"\n" - " \"NONE\"\n" - " \"SINGLE\"\n" - " \"ALL|ANYONECANPAY\"\n" - " \"NONE|ANYONECANPAY\"\n" - " \"SINGLE|ANYONECANPAY\""}, - {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"}, - {"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible"}, + {"options|sighashtype", {RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Type::STR}, RPCArg::Optional::OMITTED, "", + { + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + " \"DEFAULT\"\n" + " \"ALL\"\n" + " \"NONE\"\n" + " \"SINGLE\"\n" + " \"ALL|ANYONECANPAY\"\n" + " \"NONE|ANYONECANPAY\"\n" + " \"SINGLE|ANYONECANPAY\"", + RPCArgOptions{.also_positional = true}}, + {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them", RPCArgOptions{.also_positional = true}}, + {"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible", RPCArgOptions{.also_positional = true}}, + }, + RPCArgOptions{.oneline_description="options"}}, + {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "for backwards compatibility", RPCArgOptions{.hidden=true}}, + {"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "for backwards compatibility", RPCArgOptions{.hidden=true}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -1970,9 +1977,39 @@ RPCHelpMan descriptorprocesspsbt() EvalDescriptorStringOrObject(descs[i], provider, /*expand_priv=*/true); } - int sighash_type = ParseSighashString(request.params[2]); - bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool(); - bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool(); + // Get options + bool bip32derivs = true; + bool finalize = true; + int sighash_type = ParseSighashString(NullUniValue); // Use ParseSighashString default + if (request.params[2].isStr() || request.params[2].isNull()) { + // Old style positional parameters + sighash_type = ParseSighashString(request.params[2]); + bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool(); + finalize = request.params[4].isNull() ? true : request.params[4].get_bool(); + } else { + // New style options are in an object + UniValue options = request.params[2]; + RPCTypeCheckObj(options, + { + {"bip32derivs", UniValueType(UniValue::VBOOL)}, + {"finalize", UniValueType(UniValue::VBOOL)}, + {"sighashtype", UniValueType(UniValue::VSTR)}, + }, + true, true); + if (options.exists("bip32derivs")) { + bip32derivs = options["bip32derivs"].get_bool(); + } + if (options.exists("finalize")) { + finalize = options["finalize"].get_bool(); + } + if (options.exists("sighashtype")) { + sighash_type = ParseSighashString(options["sighashtype"]); + } + if (request.params.size() > 3) { + // Same behaviour as too many args passed normally + throw std::runtime_error(self.ToString()); + } + } const PartiallySignedTransaction& psbtx = ProcessPSBT( request.params[0].get_str(), diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 8042bdf071..706cc1e3c6 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -1027,11 +1027,14 @@ class PSBTTest(BitcoinTestFramework): # are still added to the psbt alt_descriptor = descsum_create(f"wpkh({get_generate_key().privkey})") alt_psbt = self.nodes[2].descriptorprocesspsbt(psbt=psbt, descriptors=[alt_descriptor], sighashtype="ALL")["psbt"] + assert_equal(alt_psbt, self.nodes[2].descriptorprocesspsbt(psbt=psbt, descriptors=[alt_descriptor], options={'sighashtype': "ALL"})["psbt"]) decoded = self.nodes[2].decodepsbt(alt_psbt) test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo']) # Test that the psbt is not finalized and does not have bip32_derivs unless specified processed_psbt = self.nodes[2].descriptorprocesspsbt(psbt=psbt, descriptors=[descriptor], sighashtype="ALL", bip32derivs=True, finalize=False) + assert_equal(processed_psbt, self.nodes[2].descriptorprocesspsbt(psbt=psbt, descriptors=[descriptor], options={'sighashtype': "ALL", 'bip32derivs': True, 'finalize': False})) + assert_equal(processed_psbt, self.nodes[2].descriptorprocesspsbt(psbt, [descriptor], "ALL", True, False)) decoded = self.nodes[2].decodepsbt(processed_psbt['psbt']) test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo', 'partial_signatures', 'bip32_derivs']) @@ -1039,6 +1042,7 @@ class PSBTTest(BitcoinTestFramework): assert "hex" not in processed_psbt processed_psbt = self.nodes[2].descriptorprocesspsbt(psbt=psbt, descriptors=[descriptor], sighashtype="ALL", bip32derivs=False, finalize=True) + assert_equal(processed_psbt, self.nodes[2].descriptorprocesspsbt(psbt, [descriptor], {'sighashtype': "ALL", 'bip32derivs': False, 'finalize': True})) decoded = self.nodes[2].decodepsbt(processed_psbt['psbt']) test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo', 'final_scriptwitness']) @@ -1050,6 +1054,8 @@ 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") + assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], "all") + assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], {'sighashtype': "all"}) if __name__ == '__main__':