doc: Update multisig-tutorial.md to use multipath descriptors

Update doc/multisig-tutorial.md to use a single multipath descriptor
instead of separate external/internal descriptors, per PR #22838.
Extract one xpub per participant, build a multipath descriptor with
<0;1> change index, and use getdescriptorinfo to append the checksum.
Clarify importdescriptors expands multipath descriptors into internal
and external forms. Tested shell snippets to confirm equivalent
listdescriptors output as the two-descriptor method.

Added missing loadwallet command for multisig_wallet_01

test: Use multipath descriptors in the functional wallet test
wallet_multisig_descriptor_psbt as this is intended as documentation

doc: replace `bitcoin-cli` with `bitcoin rpc` in multisig-tutorial.md

removed -named parameter where possible.

fixed a couple bugs where -signet was not passed

the call to getcoins.py requires the bitcoin-cli command still
This commit is contained in:
Ben Westgate
2025-09-02 15:14:52 -05:00
parent 7e58c94112
commit 2a46e94a16
2 changed files with 50 additions and 62 deletions

View File

@@ -25,12 +25,13 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
self.skip_if_no_wallet()
@staticmethod
def _get_xpub(wallet, internal):
def _get_xpub(wallet):
"""Extract the wallet's xpubs using `listdescriptors` and pick the one from the `pkh` descriptor since it's least likely to be accidentally reused (legacy addresses)."""
pkh_descriptor = next(filter(lambda d: d["desc"].startswith("pkh(") and d["internal"] == internal, wallet.listdescriptors()["descriptors"]))
pkh_descriptor = next(filter(lambda d: d["desc"].startswith("pkh(") and not d["internal"], wallet.listdescriptors()["descriptors"]))
# Keep all key origin information (master key fingerprint and all derivation steps) for proper support of hardware devices
# See section 'Key origin identification' in 'doc/descriptors.md' for more details...
return pkh_descriptor["desc"].split("pkh(")[1].split(")")[0]
# Replace the change index with the multipath convention
return pkh_descriptor["desc"].split("pkh(")[1].split(")")[0].replace("/0/*", "/<0;1>/*")
@staticmethod
def _check_psbt(psbt, to, value, multisig):
@@ -44,26 +45,19 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
amount += vout["value"]
assert_approx(amount, float(value), vspan=0.001)
def participants_create_multisigs(self, external_xpubs, internal_xpubs):
def participants_create_multisigs(self, xpubs):
"""The multisig is created by importing the following descriptors. The resulting wallet is watch-only and every participant can do this."""
for i, node in enumerate(self.nodes):
node.createwallet(wallet_name=f"{self.name}_{i}", blank=True, disable_private_keys=True)
multisig = node.get_wallet_rpc(f"{self.name}_{i}")
external = multisig.getdescriptorinfo(f"wsh(sortedmulti({self.M},{','.join(external_xpubs)}))")
internal = multisig.getdescriptorinfo(f"wsh(sortedmulti({self.M},{','.join(internal_xpubs)}))")
multisig_desc = f"wsh(sortedmulti({self.M},{','.join(xpubs)}))"
checksum = multisig.getdescriptorinfo(multisig_desc)["checksum"]
result = multisig.importdescriptors([
{ # receiving addresses (internal: False)
"desc": external["descriptor"],
{ # Multipath descriptor expands to receive and change
"desc": f"{multisig_desc}#{checksum}",
"active": True,
"internal": False,
"timestamp": "now",
},
{ # change addresses (internal: True)
"desc": internal["descriptor"],
"active": True,
"internal": True,
"timestamp": "now",
},
}
])
assert all(r["success"] for r in result)
yield multisig
@@ -84,10 +78,10 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
}
self.log.info("Generate and exchange xpubs...")
external_xpubs, internal_xpubs = [[self._get_xpub(signer, internal) for signer in participants["signers"]] for internal in [False, True]]
xpubs = [self._get_xpub(signer) for signer in participants["signers"]]
self.log.info("Every participant imports the following descriptors to create the watch-only multisig...")
participants["multisigs"] = list(self.participants_create_multisigs(external_xpubs, internal_xpubs))
participants["multisigs"] = list(self.participants_create_multisigs(xpubs))
self.log.info("Check that every participant's multisig generates the same addresses...")
for _ in range(10): # we check that the first 10 generated addresses are the same for all participant's multisigs