doc: update multisig tutorial to use multipath descriptors #33286

pull BenWestgate wants to merge 1 commits into bitcoin:master from BenWestgate:change-and-receiving-single-descriptor changing 2 files +50 −62
  1. BenWestgate commented at 8:28 pm on September 2, 2025: contributor

    Summary

    Update doc/multisig-tutorial.md to use multipath descriptor format
    instead of separate external/internal descriptors. The tutorial now:

    • extracts a single xpub_n per participant
    • constructs a multipath wsh(sortedmulti(...)) descriptor with <0;1>
      change index semantics
    • uses getdescriptorinfo to compute descriptor checksum
    • explains that importdescriptors expands the multipath descriptor
      into internal and external descriptors
    • update /test/functional/wallet_multisig_descriptor_psbt.py functional test / documentation to use multi-path descriptors

    Motivation

    A single multipath descriptor is the most convenient pattern for multisig; our documentation should use it.


    What changed

    • replaced extraction of external_xpub_n and internal_xpub_n with
      extraction of a single xpub_n
    • removed instructions to create and import separate external/internal
      descriptors
    • added instructions to build a multipath wsh(sortedmulti(...))
      descriptor and derive checksum with getdescriptorinfo
    • checksum field is parsed and appended as the multipath descriptor is not the canonical “desc” output
    • clarified that importdescriptors automatically expands multipath
      descriptors into internal and external forms
    • similar changes to the functional test: wallet_multisig_descriptor_psbt.

    Testing

    I have run the updated shell snippets and confirmed the multipath descriptor produces the same listdescriptors output after importing as the two descriptor method in bitcoin:master.


    This tutorial change references the multipath descriptor consolidation (see commit / PR referenced in the change). The commit message points to bitcoin#22838 as the upstream change that enables this behavior.


    Release note (for changelog)

    Documentation: update multisig tutorial and multisig functional test to use multipath descriptors

  2. DrahtBot added the label Docs on Sep 2, 2025
  3. DrahtBot commented at 8:28 pm on September 2, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33286.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK rkrux, Sjors
    Concept ACK w0xlt
    Stale ACK kannapoix

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    No conflicts as of last run.

  4. BenWestgate marked this as a draft on Sep 2, 2025
  5. BenWestgate marked this as ready for review on Sep 2, 2025
  6. Sjors commented at 10:06 am on September 3, 2025: member

    Concept ACK

    #32784 contains a similar change, but it’s nice to do that separately and earlier.

    You could probably just squash these commits.

    Can you also update https://github.com/bitcoin/bitcoin/blob/master/test/functional/wallet_multisig_descriptor_psbt.py to use multipath descriptors? That test is quite similar to this document.

  7. w0xlt commented at 7:06 pm on September 3, 2025: contributor
    Concept ACK
  8. in doc/multisig-tutorial.md:80 in cf2986684b outdated
    87 
    88-`external_desc` and `internal_desc` specify the output type (`wsh`, in this case) and the xpubs involved. They also use BIP 67 (`sortedmulti`), so the wallet can be recreated without worrying about the order of xpubs. Conceptually, descriptors describe a list of scriptPubKey (along with information for spending from it) [[source](https://github.com/bitcoin/bitcoin/issues/21199#issuecomment-780772418)].
    89+`desc` specifies the output type (`wsh`, in this case) and the xpubs involved. It also uses BIP 67 (`sortedmulti`), so the wallet can be recreated without worrying about the order of xpubs. Conceptually, descriptors describe a list of scriptPubKey (along with information for spending from it) [[source](https://github.com/bitcoin/bitcoin/issues/21199#issuecomment-780772418)].
    90 
    91-Note that at least two descriptors are usually used, one for internal derivation paths and one for external ones. There are discussions about eliminating this redundancy, as can be seen in the issue [#17190](https://github.com/bitcoin/bitcoin/issues/17190).
    92+Note that previously at least two descriptors were usually used, one for internal derivation paths and one for external ones. Since https://github.com/bitcoin/bitcoin/pull/22838 this redundancy has been eliminated by a multipath descriptor with <code><0;1></code> as the change index.
    


    rkrux commented at 2:12 pm on September 4, 2025:

    In cf2986684ba0db4e0f7b32deb65d7767f80e9c01 “Update multisig-tutorial.md to use multipath descriptors”

    Can consider moving this note in the previous section where the multipath descriptor is actually sed-ed.

     0diff --git a/doc/multisig-tutorial.md b/doc/multisig-tutorial.md
     1index fa6366df91..c59359de0a 100644
     2--- a/doc/multisig-tutorial.md
     3+++ b/doc/multisig-tutorial.md
     4@@ -63,6 +63,8 @@ for x in "${!xpubs[@]}"; do printf "[%s]=%s\n" "$x" "${xpubs[$x]}" ; done
     5 
     6 As previously mentioned, this step extracts the `m/84'/1'/0'` account instead of the path defined in [BIP 45](https://github.com/bitcoin/bips/blob/master/bip-0045.mediawiki) or [BIP 87](https://github.com/bitcoin/bips/blob/master/bip-0087.mediawiki), since there is no way to extract a specific path in Bitcoin Core at the time of writing.
     7 
     8+Note that previously at least two descriptors were usually used, one for internal derivation paths and one for external ones. Since [#22838](/bitcoin-bitcoin/22838/) this redundancy has been eliminated by a multipath descriptor with <code><0;1></code> as the change index.
     9+
    10 ### 1.2 Define the Multisig Descriptor
    11 
    12 Define the multisig descriptor, add the checksum and then, wrap it in a JSON array.
    13@@ -77,8 +79,6 @@ multisig_desc="[{\"desc\": \"${desc}#${checksum}\", \"active\": true, \"timestam
    14 
    15 `desc` specifies the output type (`wsh`, in this case) and the xpubs involved. It also uses BIP 67 (`sortedmulti`), so the wallet can be recreated without worrying about the order of xpubs. Conceptually, descriptors describe a list of scriptPubKey (along with information for spending from it) [[source](/bitcoin-bitcoin/21199/#issuecomment-780772418)].
    16 
    17-Note that previously at least two descriptors were usually used, one for internal derivation paths and one for external ones. Since [#22838](/bitcoin-bitcoin/22838/) this redundancy has been eliminated by a multipath descriptor with <code><0;1></code> as the change index.
    18-
    19 After creating the descriptor, it is necessary to add the checksum, which is required by the `importdescriptors` RPC.
    20 
    21 The checksum for a descriptor without one can be computed using the `getdescriptorinfo` RPC. The response has the `descriptor` field, which is the descriptor with the checksum added.
    

    BenWestgate commented at 4:25 am on September 6, 2025:
    Good idea. I moved that note to section 1.1, right before our snippet with sed. I’d rather explain it in words before the command.
  9. rkrux commented at 2:13 pm on September 4, 2025: contributor

    Concept ACK 226836e70501abf9ac2434900e74eec31edd716e

    As mentioned in this comment #33286 (comment) as well, I think all these commits can be squashed into one.

  10. kannapoix commented at 6:05 am on September 5, 2025: none

    ACK 226836e70501abf9ac2434900e74eec31edd716e

    I followed the new instruction and successfully spend from multisig wallet: https://mempool.space/signet/tx/6b5b7922b68b6becfc3aea5a541ce700837d5105d332889929b46cf1428df4a4

    Although not directly related to this PR, I have a minor suggestion: it might be helpful to add a loadwallet command for multisig_wallet_01, not just for participant wallets.

    0diff --git a/doc/multisig-tutorial.md b/doc/multisig-tutorial.md
    1index fa6366df91d..3527b1cd17d 100644
    2--- a/doc/multisig-tutorial.md
    3+++ b/doc/multisig-tutorial.md
    4@@ -115,6 +115,7 @@ Once the wallets have already been created and this tutorial needs to be repeate
    5 
    6```bash
    7 for ((n=1;n<=3;n++)); do ./build/bin/bitcoin-cli -signet loadwallet "participant_${n}"; done
    8+./build/bin/bitcoin-cli -signet loadwallet "multisig_wallet_01"
    9```
    
  11. DrahtBot requested review from Sjors on Sep 5, 2025
  12. DrahtBot requested review from rkrux on Sep 5, 2025
  13. BenWestgate force-pushed on Sep 6, 2025
  14. BenWestgate force-pushed on Sep 6, 2025
  15. BenWestgate commented at 5:31 am on September 6, 2025: contributor

    Thanks for the suggestion @Sjors. I’ve updated the test to use multi-path descriptors as well.

    Although not directly related to this PR, I have a minor suggestion: it might be helpful to add a loadwallet command for multisig_wallet_01, not just for participant wallets.

    I agree it’s a flaw to not load multisig_wallet_01 so I added it to this PR, we’ll see if that’s OK or reviewers want it in a separate commit or PR.

  16. in doc/multisig-tutorial.md:118 in 5424b4f1ce outdated
    114@@ -120,6 +115,7 @@ Once the wallets have already been created and this tutorial needs to be repeate
    115 
    116 ```bash
    117 for ((n=1;n<=3;n++)); do ./build/bin/bitcoin-cli -signet loadwallet "participant_${n}"; done
    118+./build/bin/bitcoin-cli -signet loadwallet "multisig_wallet_01"
    


    Sjors commented at 6:18 am on September 8, 2025:
    nit: you could use ./build/bin/bitcoin rpc instead. This removes the need for -named elsewhere in the tutorial.

    BenWestgate commented at 8:18 pm on September 8, 2025:

    I don’t understand this nit. I tried:

    0for ((n=1;n<=3;n++))
    1do
    2 ./build/bin/bitcoin rpc -signet createwallet "participant_${n}"
    3done
    

    and got: bash: ./build/bin/bitcoin: No such file or directory


    kannapoix commented at 7:39 am on September 11, 2025:

    It looks like the bitcoin binary might not have been built.
    As far as I know, BUILD_BITCOIN_BIN=ON is the default, but maybe you built with a non-default setting.
    If you build with bitcoin binary explicitly enabled, it should be generated at the path:

    0cmake -B build -DBUILD_BITCOIN_BIN=ON
    

    Sjors commented at 10:00 am on September 11, 2025:
    It should be built by default, as of a few months ago.

    BenWestgate commented at 8:01 pm on September 12, 2025:

    Okay, I’ve updated the tutorial to use bitcoin rpc everywhere possible (./contrib/signet/getcoins.py won’t accept bitcoin rpc).

    I also fixed a bug where we forgot to specify -signet: “Documentation for these and other parameters can be found by typing…” https://github.com/bitcoin/bitcoin/pull/33286/commits/3d42607fb5966d8e3b79fdb878d59483432625d9

  17. in test/functional/wallet_multisig_descriptor_psbt.py:34 in 5424b4f1ce outdated
    32+        pkh_descriptor = next(filter(lambda d: d["desc"].startswith("pkh(") and not d["internal"], wallet.listdescriptors()["descriptors"]))
    33         # Keep all key origin information (master key fingerprint and all derivation steps) for proper support of hardware devices
    34         # See section 'Key origin identification' in 'doc/descriptors.md' for more details...
    35-        return pkh_descriptor["desc"].split("pkh(")[1].split(")")[0]
    36+        # Replace the change index with the multipath convention
    37+        return pkh_descriptor["desc"].split("pkh(")[1].split(")")[0].replace("/0/*", "/<0;1>/*")
    


    Sjors commented at 6:22 am on September 8, 2025:
    You could also drop /0/* because it’s not part of the origin. Then later on where you use the xpub, add /<0;1>/*.

    BenWestgate commented at 8:11 pm on September 8, 2025:
    Master returns xpub with change index so I replace() inline to maintain the same derivation depth. This matches the method in our Tutorial and is less code. Finally, if the future wallet ever outputs a multipath descriptor, we’ll get an xpub in this format and not have to add /<0;1>/*.
  18. Sjors approved
  19. Sjors commented at 6:23 am on September 8, 2025: member

    utACK 5424b4f1ce74c82b7ae01034bbc1592088048128

    I didn’t test the tutorial.

  20. DrahtBot requested review from kannapoix on Sep 11, 2025
  21. kannapoix commented at 7:43 am on September 11, 2025: none

    reACK 5424b4f1ce74c82b7ae01034bbc1592088048128

    Tested and reviewed the new functional test.

  22. in doc/multisig-tutorial.md:180 in 3d42607fb5 outdated
    176@@ -183,9 +177,9 @@ Optionally, the PSBT can be decoded to a JSON format using `decodepsbt` RPC.
    177 The `analyzepsbt` RPC analyzes and provides information about the current status of a PSBT and its inputs, e.g. missing signatures.
    178 
    179 ```bash
    180-./build/bin/bitcoin-cli -signet decodepsbt $funded_psbt
    181+./build/bin/bitcoin rpc -signet decodepsbt $funded_psbt
    


    kannapoix commented at 5:49 am on September 18, 2025:

    This command may result in an error like:

    0error code: -8
    1error message:
    2Unknown named parameter cHNidP8BAH0CAAAAASMcBn5VJaIET6lL4b76V676ffyrvpxT6UI++gY14LwWAQAAAAD9////AoAaBgAAAAAAFgAUhv4Mbo6zEszAaF4zBry18DXCw+CuhQEAAAAAACIAIKUhPbGKzw2gMuSI/kFtVa5EYyTBZdlS7zeuyJg5kJ2kAAAAAAABAH0CAAAAARULOYOh2W+IbDULcj5n6NF4uzQx8ZcqXa+YrNgx+Y/XmwMAAAD9////AqQGAAAAAAAAFgAUvI6wMmXzdKiS7/nhen+pYfbaY8MgoQcAAAAAACIAILvTfOv022A80QUkWRjFA2e0RkHffMZf5J8TeDWo3ajRIB8EAAEBKyChBwAAAAAAIgAgu9N86/TbYDzRBSRZGMUDZ7RGQd98xl/knxN4NajdqNEBBWlSIQJJlgJ1txTezfeaWmWQOhvtX8QpacAatIPHwclsdFKSZSECnQKWtfbPyvYEekbMRgQjGQHLD4vD/Cknn12bTH1WFRkhA7q6xvV1rI/SuSmR/3VAoEd6wymtY0S3GOyp+dNgZhSSU64iBgJJlgJ1txTezfeaWmWQOhvtX8QpacAatIPHwclsdFKSZRgdWhVBVAAAgAEAAIAAAACAAAAAAAAAAAAiBgKdApa19s/K9gR6RsxGBCMZAcsPi8P8KSefXZtMfVYVGRhsho9WVAAAgAEAAIAAAACAAAAAAAAAAAAiBgO6usb1dayP0rkpkf91QKBHesMprWNEtxjsqfnTYGYUkhh47RKgVAAAgAEAAIAAAACAAAAAAAAAAAAAAAEBaVIhArIbxpgn95IGTodGk2VVW6kpXgW6sI7rnSuJeYyeSpAKIQMkA2pAaaug/2ZQvQspY1KMldrpV2E38WBI5gk/+rZsmiEDJbyWjQ7C1EB74/9f2H140782VImwBhoB9OyC0hUQGRdTriICArIbxpgn95IGTodGk2VVW6kpXgW6sI7rnSuJeYyeSpAKGHjtEqBUAACAAQAAgAAAAIABAAAAAAAAACICAyQDakBpq6D/ZlC9CyljUoyV2ulXYTfxYEjmCT/6tmyaGB1aFUFUAACAAQAAgAAAAIABAAAAAAAAACICAyW8lo0OwtRAe+P/X9h9eNO/NlSJsAYaAfTsgtIVEBkXGGyGj1ZUAACAAQAAgAAAAIABAAAAAAAAAAA
    

    IIUC, you may need to use ‎-nonamed when passing parameters that include ‎=. A PSBT may contain ‎= as base64 padding.

    https://github.com/bitcoin/bitcoin/blob/3d42607fb5966d8e3b79fdb878d59483432625d9/src/bitcoin.cpp#L86-L91

    0./build/bin/bitcoin rpc -nonamed -signet decodepsbt $funded_psbt
    

    or

    0./build/bin/bitcoin rpc -signet decodepsbt psbt=$funded_psbt
    

    We also need to apply the same fix as for decodepsbt to analyzepsbt, walletprocesspsbt, combinepsbt, and finalizepsbt.

    Note: Quoting a PSBT didn’t solve the issue.

    0./build/bin/bitcoin rpc -signet decodepsbt "$funded_psbt"
    

    Sjors commented at 8:20 am on September 18, 2025:
    That might be a case of #32821. I would just wait until that’s merged.
  23. in test/functional/wallet_multisig_descriptor_psbt.py:56 in 5424b4f1ce outdated
    56+            multisig_desc = f"wsh(sortedmulti({self.M},{','.join(xpubs)}))"
    57+            checksum = multisig.getdescriptorinfo(multisig_desc)["checksum"]
    58             result = multisig.importdescriptors([
    59-                {  # receiving addresses (internal: False)
    60-                    "desc": external["descriptor"],
    61+                {  # Multipath descriptor expands to change and receive
    


    rkrux commented at 2:48 pm on September 24, 2025:

    In 5424b4f1ce74c82b7ae01034bbc1592088048128 “doc: Update multisig-tutorial.md to use multipath descriptors”

    Nit: By convention, the first index of the two corresponds to the receive address and the other being the change address as quoted in the above tutorial too: doc/multisig-tutorial.md.

    0- # Multipath descriptor expands to change and receive
    1+ # Multipath descriptor expands to receive and change 
    
  24. in doc/multisig-tutorial.md:47 in 5424b4f1ce outdated
    43@@ -44,14 +44,14 @@ wpkh([1004658e/84'/1'/0']tpubDCBEcmVKbfC9KfdydyLbJ2gfNL88grZu1XcWSW9ytTM6fitvaRm
    44 The suffix (after #) is the checksum. Descriptors can optionally be suffixed with a checksum to protect against typos or copy-paste errors.
    45 All RPCs in Bitcoin Core will include the checksum in their output.
    46 
    47+Note that previously at least two descriptors were usually used, one for internal derivation paths and one for external ones. Since https://github.com/bitcoin/bitcoin/pull/22838 this redundancy has been eliminated by a multipath descriptor with <code><0;1></code> as the change index.
    


    rkrux commented at 2:52 pm on September 24, 2025:

    In 5424b4f1ce74c82b7ae01034bbc1592088048128 “doc: Update multisig-tutorial.md to use multipath descriptors”

    with <0;1> as the change index.

    I find it confusing to read it as the change index when one of the indices is non-change (receive) when expanded later. Maybe the below one?

    0- with <code><0;1></code> as the change index.
    1+ with <code><0;1></code> as the derivation index. 
    

    Nit: To keep it consistent with how the multipath is expanded.

    0- , one for internal derivation paths and one for external ones.
    1+ , one for external derivation paths and one for internal ones. 
    

    BenWestgate commented at 7:18 am on October 6, 2025:

    I updated the note with your feedback and implemented the comment nit in the test. Does this address your concern?

    Note that previously at least two descriptors were usually used, one for external derivation paths and one for internal ones. Since #22838 this redundancy has been eliminated by a multipath descriptor with <0;1> at the BIP-44 change level expanding to external and internal descriptors when imported.

    The term used in BIP-44 is “change level” which is why I used that word to match that standard, but I agree it’s confusing without reference that the change level determines whether the chain is internal or external.


    rkrux commented at 8:27 am on October 6, 2025:
    Yes, this handles it. Thank you.
  25. rkrux commented at 2:54 pm on September 24, 2025: contributor
    crACK 3d42607fb5966d8e3b79fdb878d59483432625d9
  26. DrahtBot requested review from Sjors on Sep 24, 2025
  27. DrahtBot requested review from kannapoix on Sep 24, 2025
  28. 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
    2a46e94a16
  29. BenWestgate force-pushed on Oct 6, 2025
  30. rkrux approved
  31. rkrux commented at 8:27 am on October 6, 2025: contributor
    crACK 2a46e94
  32. Sjors commented at 9:19 am on October 6, 2025: member
    utACK 2a46e94a1600a4f28e01db23a89f039acaa2c45e
  33. BenWestgate commented at 9:05 am on October 7, 2025: contributor

    This isn’t quite true for multipath descriptors:

    The checksum for a descriptor without one can be computed using the getdescriptorinfo RPC. The response has the descriptor field, which is the descriptor with the checksum added.

    We have to take the checksum field and append it to the multipath input descriptor, similar to private key descriptors.

    Do we need to document this or are the code snippets that do this enough?


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-10-10 15:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me