diff --git a/doc/multisig-tutorial.md b/doc/multisig-tutorial.md
index fd74e62ed28f..0a9379d9caf7 100644
--- a/doc/multisig-tutorial.md
+++ b/doc/multisig-tutorial.md
@@ -20,15 +20,27 @@ This tutorial also uses the default PKH derivation path to get the xpubs and doe
For a 2-of-3 multisig, create 3 wallets. These wallets contain HD seed and private keys, which will be used to sign the PSBTs and derive the xpub.
-These three wallets should not be used directly for privacy reasons (public key reuse). They should only be used to sign transactions for the (watch-only) multisig wallet.
+These three wallets should not be used directly for privacy reasons (public key reuse). They should only be used to sign transactions for the (watch-only) multisig wallet. To make that less likely to happen by accident, participant wallets only have singlesig legacy addresses, and no singlesig Bech32 addresses.
+
+`addhdkey` adds an HD key to the blank wallet, which `createwalletdescriptor` then uses:
```bash
for ((n=1;n<=3;n++))
do
- ./build/bin/bitcoin rpc -signet createwallet "participant_${n}"
+ ./build/bin/bitcoin rpc -signet -named createwallet wallet_name="participant_${n}" blank=true
+ ./build/bin/bitcoin rpc -signet -rpcwallet="participant_${n}" addhdkey
+ ./build/bin/bitcoin rpc -signet -rpcwallet="participant_${n}" createwalletdescriptor legacy
done
```
+The `legacy` descriptor is only there to hold the private keys: it derives from `m/44h/1h/0h/<0;1>/*`, the same paths as the multisig descriptor defined below, and a wallet can only sign for a derivation path that one of its descriptors uses.
+
+A later step spends from the multisig wallet, so create one more wallet to receive that payment. This one is an ordinary singlesig wallet, standing in for whoever is being paid:
+
+```bash
+./build/bin/bitcoin rpc -signet createwallet "recipient"
+```
+
Extract the xpub of each wallet. To do this, the `derivehdkey` RPC is used.
Note that previously at least two descriptors were usually used, one for external derivation paths and one for internal ones. Since https://github.com/bitcoin/bitcoin/pull/22838 this redundancy has been eliminated by a multipath descriptor with <0;1> at the [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#change) change level expanding to external and internal descriptors when imported.
@@ -100,6 +112,7 @@ Once the wallets have already been created and this tutorial needs to be repeate
```bash
for ((n=1;n<=3;n++)); do ./build/bin/bitcoin rpc -signet loadwallet "participant_${n}"; done
./build/bin/bitcoin rpc -signet loadwallet "multisig_wallet_01"
+./build/bin/bitcoin rpc -signet loadwallet "recipient"
```
### 1.4 Fund the wallet
@@ -138,7 +151,7 @@ PSBT is a data format that allows wallets and other tools to exchange informatio
The current PSBT version (v0) is defined in [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki).
-For simplicity, the destination address is taken from the `participant_1` wallet in the code above, but it can be any valid bitcoin address.
+The destination address is taken from the `recipient` wallet.
The `walletcreatefundedpsbt` RPC is used to create and fund a transaction in the PSBT format. It is the first step in creating the PSBT.
@@ -147,7 +160,7 @@ balance=$(./build/bin/bitcoin rpc -signet -rpcwallet="multisig_wallet_01" getbal
amount=$(echo "$balance * 0.8" | bc -l | sed -e 's/^\./0./' -e 's/^-\./-0./')
-destination_addr=$(./build/bin/bitcoin rpc -signet -rpcwallet="participant_1" getnewaddress)
+destination_addr=$(./build/bin/bitcoin rpc -signet -rpcwallet="recipient" getnewaddress "" bech32m)
funded_psbt=$(./build/bin/bitcoin rpc -signet -rpcwallet="multisig_wallet_01" walletcreatefundedpsbt outputs="{\"$destination_addr\": $amount}" | jq -r '.psbt')
```
diff --git a/doc/release-notes-32857.md b/doc/release-notes-32857.md
new file mode 100644
index 000000000000..24cdb811e794
--- /dev/null
+++ b/doc/release-notes-32857.md
@@ -0,0 +1,8 @@
+Updated RPCs
+------------
+
+- The `send`, `sendall`, `walletprocesspsbt`, `walletcreatefundedpsbt`, and
+ `descriptorprocesspsbt` RPCs now accept a `keypath_only` option. When enabled,
+ they do not add new Taproot script-path data, sign script paths, or finalize
+ script-path spends. Existing script-path data and signatures remain in a
+ supplied PSBT. (#32857)
diff --git a/doc/release-notes-32861.md b/doc/release-notes-32861.md
new file mode 100644
index 000000000000..9030ee57f9f9
--- /dev/null
+++ b/doc/release-notes-32861.md
@@ -0,0 +1,9 @@
+Updated RPCs
+------------
+
+- `createwalletdescriptor` no longer requires the `hdkey` argument when the
+ wallet has no active descriptors but does have exactly one HD key added with
+ `addhdkey`. Previously the user had to look the key up with `listdescriptors`
+ and pass it in. This makes it easier to set up a blank wallet that only has
+ the descriptors it actually needs, which `doc/multisig-tutorial.md` now
+ demonstrates. (#32861)
diff --git a/src/common/types.h b/src/common/types.h
index 1ffcd392d609..efcebc9062df 100644
--- a/src/common/types.h
+++ b/src/common/types.h
@@ -48,6 +48,12 @@ struct PSBTFillOptions {
* Whether to fill in bip32 derivation information if available.
*/
bool bip32_derivs{true};
+
+ /**
+ * Only add new Taproot key-path data, and only sign and finalize Taproot
+ * inputs using the key path. Existing script-path data is left intact.
+ */
+ bool taproot_keypath_only{false};
};
} // namespace common
diff --git a/src/core_io.cpp b/src/core_io.cpp
index 3650d70810a9..cbaf24171237 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 904f5a8643b9..d78706216c20 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/external_signer.cpp b/src/external_signer.cpp
index cb49cedc69c2..c3e989466891 100644
--- a/src/external_signer.cpp
+++ b/src/external_signer.cpp
@@ -8,10 +8,13 @@
#include
#include
#include
+#include