aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xci/test/06_script_b.sh13
-rw-r--r--configure.ac15
-rw-r--r--doc/dependencies.md2
-rw-r--r--src/random.cpp33
-rw-r--r--src/rpc/client.cpp3
-rw-r--r--src/rpc/rawtransaction.cpp93
-rw-r--r--src/rpc/util.cpp5
-rw-r--r--src/rpc/util.h2
-rw-r--r--src/test/fuzz/fuzz.cpp27
-rw-r--r--src/test/fuzz/rpc.cpp1
-rw-r--r--src/util/fs.h2
-rwxr-xr-xtest/functional/rpc_psbt.py47
12 files changed, 183 insertions, 60 deletions
diff --git a/ci/test/06_script_b.sh b/ci/test/06_script_b.sh
index 83da4dac32..ce5163caa8 100755
--- a/ci/test/06_script_b.sh
+++ b/ci/test/06_script_b.sh
@@ -24,6 +24,11 @@ if [ "$RUN_FUZZ_TESTS" = "true" ]; then
if [ ! -d "$DIR_FUZZ_IN" ]; then
git clone --depth=1 https://github.com/bitcoin-core/qa-assets "${DIR_QA_ASSETS}"
fi
+ (
+ cd "${DIR_QA_ASSETS}"
+ echo "Using qa-assets repo from commit ..."
+ git log -1
+ )
elif [ "$RUN_UNIT_TESTS" = "true" ] || [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then
export DIR_UNIT_TEST_DATA=${DIR_QA_ASSETS}/unit_test_data/
if [ ! -d "$DIR_UNIT_TEST_DATA" ]; then
@@ -101,14 +106,6 @@ cd "${BASE_BUILD_DIR}/bitcoin-$HOST"
bash -c "./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG" || ( (cat config.log) && false)
-if [[ ${USE_MEMORY_SANITIZER} == "true" ]]; then
- # MemorySanitizer (MSAN) does not support tracking memory initialization done by
- # using the Linux getrandom syscall. Avoid using getrandom by undefining
- # HAVE_SYS_GETRANDOM. See https://github.com/google/sanitizers/issues/852 for
- # details.
- grep -v HAVE_SYS_GETRANDOM src/config/bitcoin-config.h > src/config/bitcoin-config.h.tmp && mv src/config/bitcoin-config.h.tmp src/config/bitcoin-config.h
-fi
-
if [[ "${RUN_TIDY}" == "true" ]]; then
MAYBE_BEAR="bear --config src/.bear-tidy-config"
MAYBE_TOKEN="--"
diff --git a/configure.ac b/configure.ac
index e3b0ca3e2d..fc692c4f6a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1170,17 +1170,16 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <ctime>]],
)
dnl Check for different ways of gathering OS randomness
-AC_MSG_CHECKING([for Linux getrandom syscall])
-AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <unistd.h>
- #include <sys/syscall.h>
- #include <linux/random.h>]],
- [[ syscall(SYS_getrandom, nullptr, 32, 0); ]])],
- [ AC_MSG_RESULT([yes]); AC_DEFINE([HAVE_SYS_GETRANDOM], [1], [Define this symbol if the Linux getrandom system call is available]) ],
+AC_MSG_CHECKING([for Linux getrandom function])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+ #include <sys/random.h>]],
+ [[ getrandom(nullptr, 32, 0); ]])],
+ [ AC_MSG_RESULT([yes]); AC_DEFINE([HAVE_GETRANDOM], [1], [Define this symbol if the Linux getrandom function call is available]) ],
[ AC_MSG_RESULT([no])]
)
-AC_MSG_CHECKING([for getentropy via random.h])
-AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <unistd.h>
+AC_MSG_CHECKING([for getentropy via sys/random.h])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <sys/random.h>]],
[[ getentropy(nullptr, 32) ]])],
[ AC_MSG_RESULT([yes]); AC_DEFINE([HAVE_GETENTROPY_RAND], [1], [Define this symbol if the BSD getentropy system call is available with sys/random.h]) ],
diff --git a/doc/dependencies.md b/doc/dependencies.md
index 4344ed259e..804f796abe 100644
--- a/doc/dependencies.md
+++ b/doc/dependencies.md
@@ -20,7 +20,7 @@ You can find installation instructions in the `build-*.md` file for your platfor
| [Boost](../depends/packages/boost.mk) | [link](https://www.boost.org/users/download/) | [1.81.0](https://github.com/bitcoin/bitcoin/pull/26557) | [1.64.0](https://github.com/bitcoin/bitcoin/pull/22320) | No |
| [libevent](../depends/packages/libevent.mk) | [link](https://github.com/libevent/libevent/releases) | [2.1.12-stable](https://github.com/bitcoin/bitcoin/pull/21991) | [2.1.8](https://github.com/bitcoin/bitcoin/pull/24681) | No |
| glibc | [link](https://www.gnu.org/software/libc/) | N/A | [2.27](https://github.com/bitcoin/bitcoin/pull/27029) | Yes |
-| Linux Kernel | [link](https://www.kernel.org/) | N/A | 3.2.0 | Yes |
+| Linux Kernel | [link](https://www.kernel.org/) | N/A | [3.17.0](https://github.com/bitcoin/bitcoin/pull/27699) | Yes |
## Optional
diff --git a/src/random.cpp b/src/random.cpp
index f4c51574cc..54500e6cc6 100644
--- a/src/random.cpp
+++ b/src/random.cpp
@@ -28,14 +28,10 @@
#include <sys/time.h>
#endif
-#ifdef HAVE_SYS_GETRANDOM
-#include <sys/syscall.h>
-#include <linux/random.h>
-#endif
-#if defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX)
-#include <unistd.h>
+#if defined(HAVE_GETRANDOM) || (defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX))
#include <sys/random.h>
#endif
+
#ifdef HAVE_SYSCTL_ARND
#include <sys/sysctl.h>
#endif
@@ -252,7 +248,7 @@ static void Strengthen(const unsigned char (&seed)[32], SteadyClock::duration du
/** Fallback: get 32 bytes of system entropy from /dev/urandom. The most
* compatible way to get cryptographic randomness on UNIX-ish platforms.
*/
-static void GetDevURandom(unsigned char *ent32)
+[[maybe_unused]] static void GetDevURandom(unsigned char *ent32)
{
int f = open("/dev/urandom", O_RDONLY);
if (f == -1) {
@@ -285,23 +281,14 @@ void GetOSRand(unsigned char *ent32)
RandFailure();
}
CryptReleaseContext(hProvider, 0);
-#elif defined(HAVE_SYS_GETRANDOM)
+#elif defined(HAVE_GETRANDOM)
/* Linux. From the getrandom(2) man page:
* "If the urandom source has been initialized, reads of up to 256 bytes
* will always return as many bytes as requested and will not be
* interrupted by signals."
*/
- int rv = syscall(SYS_getrandom, ent32, NUM_OS_RANDOM_BYTES, 0);
- if (rv != NUM_OS_RANDOM_BYTES) {
- if (rv < 0 && errno == ENOSYS) {
- /* Fallback for kernel <3.17: the return value will be -1 and errno
- * ENOSYS if the syscall is not available, in that case fall back
- * to /dev/urandom.
- */
- GetDevURandom(ent32);
- } else {
- RandFailure();
- }
+ if (getrandom(ent32, NUM_OS_RANDOM_BYTES, 0) != NUM_OS_RANDOM_BYTES) {
+ RandFailure();
}
#elif defined(__OpenBSD__)
/* OpenBSD. From the arc4random(3) man page:
@@ -311,16 +298,10 @@ void GetOSRand(unsigned char *ent32)
The function call is always successful.
*/
arc4random_buf(ent32, NUM_OS_RANDOM_BYTES);
- // Silence a compiler warning about unused function.
- (void)GetDevURandom;
#elif defined(HAVE_GETENTROPY_RAND) && defined(MAC_OSX)
- /* getentropy() is available on macOS 10.12 and later.
- */
if (getentropy(ent32, NUM_OS_RANDOM_BYTES) != 0) {
RandFailure();
}
- // Silence a compiler warning about unused function.
- (void)GetDevURandom;
#elif defined(HAVE_SYSCTL_ARND)
/* FreeBSD, NetBSD and similar. It is possible for the call to return less
* bytes than requested, so need to read in a loop.
@@ -334,8 +315,6 @@ void GetOSRand(unsigned char *ent32)
}
have += len;
} while (have < NUM_OS_RANDOM_BYTES);
- // Silence a compiler warning about unused function.
- (void)GetDevURandom;
#else
/* Fall back to /dev/urandom if there is no specific method implemented to
* get system entropy for this OS.
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index f3c19003ff..d08e2d55d1 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -133,6 +133,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "walletprocesspsbt", 1, "sign" },
{ "walletprocesspsbt", 3, "bip32derivs" },
{ "walletprocesspsbt", 4, "finalize" },
+ { "descriptorprocesspsbt", 1, "descriptors"},
+ { "descriptorprocesspsbt", 3, "bip32derivs" },
+ { "descriptorprocesspsbt", 4, "finalize" },
{ "createpsbt", 0, "inputs" },
{ "createpsbt", 1, "outputs" },
{ "createpsbt", 2, "locktime" },
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 464be66046..eb0200ccf5 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -170,8 +170,9 @@ static std::vector<RPCArg> CreateTxDoc()
};
}
-// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors
-PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider)
+// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors.
+// Optionally, sign the inputs that we can using information from the descriptors.
+PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, int sighash_type, bool finalize)
{
// Unserialize the transactions
PartiallySignedTransaction psbtx;
@@ -240,9 +241,10 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
}
// Update script/keypath information using descriptor data.
- // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
- // we don't actually care about those here, in fact.
- SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, /*sighash=*/1);
+ // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures.
+ // We only actually care about those if our signing provider doesn't hide private
+ // information, as is the case with `descriptorprocesspsbt`
+ SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, sighash_type, /*out_sigdata=*/nullptr, finalize);
}
// Update script/keypath information using descriptor data.
@@ -1695,7 +1697,9 @@ static RPCHelpMan utxoupdatepsbt()
const PartiallySignedTransaction& psbtx = ProcessPSBT(
request.params[0].get_str(),
request.context,
- HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false));
+ HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false),
+ /*sighash_type=*/SIGHASH_ALL,
+ /*finalize=*/false);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
@@ -1914,6 +1918,82 @@ static RPCHelpMan analyzepsbt()
};
}
+RPCHelpMan descriptorprocesspsbt()
+{
+ return RPCHelpMan{"descriptorprocesspsbt",
+ "\nUpdate all segwit inputs in a PSBT with information from output descriptors, the UTXO set or the mempool. \n"
+ "Then, sign the inputs we are able to with information from the output descriptors. ",
+ {
+ {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
+ {"descriptors", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of either strings or objects", {
+ {"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
+ {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
+ {"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"},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "psbt", "The base64-encoded partially signed transaction"},
+ {RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[\\\"descriptor1\\\", \\\"descriptor2\\\"]\"") +
+ HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[{\\\"desc\\\":\\\"mydescriptor\\\", \\\"range\\\":21}]\"")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ // Add descriptor information to a signing provider
+ FlatSigningProvider provider;
+
+ auto descs = request.params[1].get_array();
+ for (size_t i = 0; i < descs.size(); ++i) {
+ 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();
+
+ const PartiallySignedTransaction& psbtx = ProcessPSBT(
+ request.params[0].get_str(),
+ request.context,
+ HidingSigningProvider(&provider, /*hide_secret=*/false, !bip32derivs),
+ sighash_type,
+ finalize);
+
+ // Check whether or not all of the inputs are now signed
+ bool complete = true;
+ for (const auto& input : psbtx.inputs) {
+ complete &= PSBTInputSigned(input);
+ }
+
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+
+ UniValue result(UniValue::VOBJ);
+
+ result.pushKV("psbt", EncodeBase64(ssTx));
+ result.pushKV("complete", complete);
+
+ return result;
+},
+ };
+}
+
void RegisterRawTransactionRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
@@ -1929,6 +2009,7 @@ void RegisterRawTransactionRPCCommands(CRPCTable& t)
{"rawtransactions", &createpsbt},
{"rawtransactions", &converttopsbt},
{"rawtransactions", &utxoupdatepsbt},
+ {"rawtransactions", &descriptorprocesspsbt},
{"rawtransactions", &joinpsbts},
{"rawtransactions", &analyzepsbt},
};
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 81489d7cec..d1ff3f3871 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -1126,7 +1126,7 @@ std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value)
return {low, high};
}
-std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider)
+std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider, const bool expand_priv)
{
std::string desc_str;
std::pair<int64_t, int64_t> range = {0, 1000};
@@ -1159,6 +1159,9 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl
if (!desc->Expand(i, provider, scripts, provider)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
}
+ if (expand_priv) {
+ desc->ExpandPrivate(/*pos=*/i, provider, /*out=*/provider);
+ }
std::move(scripts.begin(), scripts.end(), std::back_inserter(ret));
}
return ret;
diff --git a/src/rpc/util.h b/src/rpc/util.h
index bb5c30a2f4..3ff02582a6 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -110,7 +110,7 @@ UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_s
std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value);
/** Evaluate a descriptor given as a string, or as a {"desc":...,"range":...} object, with default range of 1000. */
-std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider);
+std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider, const bool expand_priv = false);
/** Returns, given services flags, a list of humanly readable (known) network services */
UniValue GetServicesNames(ServiceFlags services);
diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp
index 0bbfb206d5..44ba8bc254 100644
--- a/src/test/fuzz/fuzz.cpp
+++ b/src/test/fuzz/fuzz.cpp
@@ -14,14 +14,19 @@
#include <csignal>
#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
#include <exception>
#include <fstream>
#include <functional>
+#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <unistd.h>
+#include <utility>
#include <vector>
const std::function<void(const std::string&)> G_TEST_LOG_FUN{};
@@ -77,13 +82,13 @@ void initialize()
return WrappedGetAddrInfo(name, false);
};
- bool should_abort{false};
+ bool should_exit{false};
if (std::getenv("PRINT_ALL_FUZZ_TARGETS_AND_ABORT")) {
for (const auto& t : FuzzTargets()) {
if (std::get<2>(t.second)) continue;
std::cout << t.first << std::endl;
}
- should_abort = true;
+ should_exit = true;
}
if (const char* out_path = std::getenv("WRITE_ALL_FUZZ_TARGETS_AND_ABORT")) {
std::cout << "Writing all fuzz target names to '" << out_path << "'." << std::endl;
@@ -92,13 +97,23 @@ void initialize()
if (std::get<2>(t.second)) continue;
out_stream << t.first << std::endl;
}
- should_abort = true;
+ should_exit= true;
+ }
+ if (should_exit){
+ std::exit(EXIT_SUCCESS);
+ }
+ if (const auto* env_fuzz{std::getenv("FUZZ")}) {
+ // To allow for easier fuzz executable binary modification,
+ static std::string g_copy{env_fuzz}; // create copy to avoid compiler optimizations, and
+ g_fuzz_target = g_copy.c_str(); // strip string after the first null-char.
+ } else {
+ std::cerr << "Must select fuzz target with the FUZZ env var." << std::endl;
+ std::cerr << "Hint: Set the PRINT_ALL_FUZZ_TARGETS_AND_ABORT=1 env var to see all compiled targets." << std::endl;
+ std::exit(EXIT_FAILURE);
}
- Assert(!should_abort);
- g_fuzz_target = Assert(std::getenv("FUZZ"));
const auto it = FuzzTargets().find(g_fuzz_target);
if (it == FuzzTargets().end()) {
- std::cerr << "No fuzzer for " << g_fuzz_target << "." << std::endl;
+ std::cerr << "No fuzz target compiled for " << g_fuzz_target << "." << std::endl;
std::exit(EXIT_FAILURE);
}
Assert(!g_test_one_input);
diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp
index 33ffcc4cdd..6424f756a0 100644
--- a/src/test/fuzz/rpc.cpp
+++ b/src/test/fuzz/rpc.cpp
@@ -98,6 +98,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"decoderawtransaction",
"decodescript",
"deriveaddresses",
+ "descriptorprocesspsbt",
"disconnectnode",
"echo",
"echojson",
diff --git a/src/util/fs.h b/src/util/fs.h
index 886a30394e..8f79f6cba6 100644
--- a/src/util/fs.h
+++ b/src/util/fs.h
@@ -8,7 +8,7 @@
#include <tinyformat.h>
#include <cstdio>
-#include <filesystem>
+#include <filesystem> // IWYU pragma: export
#include <functional>
#include <iomanip>
#include <ios>
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 49cdfd0e66..7c653cc315 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -42,7 +42,10 @@ from test_framework.util import (
find_vout_for_address,
random_bytes,
)
-from test_framework.wallet_util import bytes_to_wif
+from test_framework.wallet_util import (
+ bytes_to_wif,
+ get_generate_key
+)
import json
import os
@@ -942,6 +945,48 @@ class PSBTTest(BitcoinTestFramework):
self.log.info("Test we don't crash when making a 0-value funded transaction at 0 fee without forcing an input selection")
assert_raises_rpc_error(-4, "Transaction requires one destination of non-0 value, a non-0 feerate, or a pre-selected input", self.nodes[0].walletcreatefundedpsbt, [], [{"data": "deadbeef"}], 0, {"fee_rate": "0"})
+ self.log.info("Test descriptorprocesspsbt updates and signs a psbt with descriptors")
+
+ self.generate(self.nodes[2], 1)
+
+ # Disable the wallet for node 2 since `descriptorprocesspsbt` does not use the wallet
+ self.restart_node(2, extra_args=["-disablewallet"])
+ self.connect_nodes(0, 2)
+ self.connect_nodes(1, 2)
+
+ key_info = get_generate_key()
+ key = key_info.privkey
+ address = key_info.p2wpkh_addr
+
+ descriptor = descsum_create(f"wpkh({key})")
+
+ txid = self.nodes[0].sendtoaddress(address, 1)
+ self.sync_all()
+ vout = find_output(self.nodes[0], txid, 1)
+
+ psbt = self.nodes[2].createpsbt([{"txid": txid, "vout": vout}], {self.nodes[0].getnewaddress(): 0.99999})
+ decoded = self.nodes[2].decodepsbt(psbt)
+ test_psbt_input_keys(decoded['inputs'][0], [])
+
+ # Test that even if the wrong descriptor is given, `witness_utxo` and `non_witness_utxo`
+ # 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"]
+ 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
+ psbt = self.nodes[2].descriptorprocesspsbt(psbt=psbt, descriptors=[descriptor], sighashtype="ALL", bip32derivs=True, finalize=False)["psbt"]
+ decoded = self.nodes[2].decodepsbt(psbt)
+ test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo', 'partial_signatures', 'bip32_derivs'])
+
+ psbt = self.nodes[2].descriptorprocesspsbt(psbt=psbt, descriptors=[descriptor], sighashtype="ALL", bip32derivs=False, finalize=True)["psbt"]
+ decoded = self.nodes[2].decodepsbt(psbt)
+ test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo', 'final_scriptwitness'])
+
+ # Broadcast transaction
+ rawtx = self.nodes[2].finalizepsbt(psbt)["hex"]
+ self.nodes[2].sendrawtransaction(rawtx)
if __name__ == '__main__':
PSBTTest().main()