Contrib: add multisig wizard #36325

pull rxbryan wants to merge 5 commits into bitcoin:master from rxbryan:contrib_multisig changing 4 files +709 −0
  1. rxbryan commented at 12:21 PM on September 24, 2026: none

    Per the discussion in #35645, this PR adds a Python utility that performs the multisig setup process, so that RPC and interface work is shaped by what that flow actually needs.

    It covers wallet creation, key generation at the BIP 87 path, collecting cosigner keys, assembling a wsh(sortedmulti()) or tr(musig(),sortedmulti_a()) descriptor, verifying it, and importing it.

    Gaps that need new functionality in C++

    • importdescriptors rejects a descriptor with no private keys when the wallet has private keys enabled. See #35377.
    • The wallet stores a multipath descriptor as two descriptors, so listdescriptors never shows the descriptor that was shared. See #36133.
    • The wallet labels addresses, not keys, so cosigner names live in the setup and are lost on import.
    • There is no RPC for validating a key expression, so the key is wrapped in pkh() for getdescriptorinfo to parse. See #35918, which exposes ParsePubkey().
    • createmultisig only takes plain public keys, not extended keys with origins, and cannot build a ranged or taproot multisig descriptor.
    • Nothing reports what a descriptor commits to, or whether this wallet holds one of its keys, before it is imported.

    Part of #35645.

  2. contrib: add multisig wizard
    Following the discussion on #36143, this commit adds a python
    utility implementing the multisig setup flow tracked in #35645
    using available RPCs and implements work arounds for missing RPC
    or wallet features.
    
    Adds wallet creation and key generation step.
    
    The wallet cannot store a name for a key, so cosigner names live
    in the setup and are lost on import.
    aaa2b87fb1
  3. contrib: validate cosigner key expressions
    Check a key expression before recording it, and reject private keys,
    derivation paths, multipath derivations, keys without origin information
    and duplicates.
    
    The wizard also requires the BIP 87 path, which the descriptor layer does
    not.
    e48f53a85a
  4. contrib: assemble wsh and taproot multisig descriptors
    Builds the ranged, multipath descriptor from the collected keys and
    a call to getdescriptorinfo checks the resulting descriptor and
    supplys the checksum.
    
    For taproot the key path is a MuSig2 aggregate of all n keys
    rather than a fixed or variable NUMS point: it cannot bypass the
    policy, since spending that way needs every signer, and it avoids
    the fingerprinting using a well-known internal key would cause.
    66fe55bc4c
  5. contrib: analyze and verify a multisig descriptor
    analyze_descriptor reports what the descriptor requires to spend,
    which cosigner we are, and the first receive address,
    
    Accepted shapes are wsh(sortedmulti(...)) and, for taproot,
    tr(KEY,sortedmulti_a(...)) where KEY is either musig() of exactly
    the keys in the leaf, so that spending by key path needs
    every cosigner, or the NUMS point, which is provably unspendable.
    Anything else is rejected.
    
    Names are local, so any cosigner we have no name for is reported
    by fingerprint.
    4e89dba064
  6. contrib: add import multisig descriptor step
    During the import process, the participant swaps their own key
    expression in the descriptor for the private one since
    importdescriptors reject a descriptor with no private keys when
    the wallet has private keys enabled. See #35377
    90a7be36a4
  7. DrahtBot added the label Scripts and tools on Sep 24, 2026
  8. DrahtBot commented at 12:21 PM on September 24, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

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

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • implements work arounds -> implements workarounds [misspelling]
    • A coordinator that does not contribute keys need on a watch-only wallet -> A coordinator that does not contribute keys needs a watch-only wallet [broken English; intended meaning is unclear without correction]

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • self.wizard.assemble(wallet, setup, 2) in test/functional/tool_multisig_wizard.py
    • self.wizard.assemble(wallet, setup, 2, "bech32m") in test/functional/tool_multisig_wizard.py
    • self.wizard.assemble(wallet, too_few, 1) in test/functional/tool_multisig_wizard.py
    • self.wizard.assemble(wallet, first, 2) in test/functional/tool_multisig_wizard.py
    • self.wizard.assemble(wallet, second, 2) in test/functional/tool_multisig_wizard.py

    Possible places where comparison-specific test macros should replace generic comparisons:

    • [test/functional/tool_multisig_wizard.py] assert keys[0] != keys[1] -> use assert_not_equal(keys[0], keys[1])
    • [test/functional/tool_multisig_wizard.py] assert summary["first_address"] != change[0] -> use assert_not_equal(summary["first_address"], change[0])

    <sup>2026-09-24 12:21:50</sup>


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: 2026-09-24 14:51 UTC

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