Skip to content

MuSig2 - #794

Draft
Sjors wants to merge 42 commits into
bitcoin-core:masterfrom
Sjors:musig2
Draft

MuSig2#794
Sjors wants to merge 42 commits into
bitcoin-core:masterfrom
Sjors:musig2

Conversation

@Sjors

@Sjors Sjors commented Jul 21, 2025

Copy link
Copy Markdown
Member

This branch starts with these two merges, in order:

  1. Optionally pass BIP388 policy to displayaddress #841 — registered descriptor address display
  2. Optionally pass BIP388 policy to signtx #792 — registered descriptor signing

Both branches include the descriptor-registration work from #842.

It is developed and tested together with:

Bitcoin Core passes HWI the ordinary multipath descriptor. HWI translates it
to the representation expected by the device and returns an opaque serialized
registration.

For Ledger, HWI constructs the BIP388 descriptor template and key information
vector internally. Registered descriptors are subsequently supplied through
--registration when displaying an address or signing a PSBT.

The general workflow is:

  • create a Bitcoin Core external-signer wallet with an active
    tr(musig(...)/<0;1>/*) descriptor;
  • call Bitcoin Core's registerdescriptor RPC, which invokes HWI's
    registerdescriptor command;
  • verify an address with walletdisplayaddress;
  • fund the wallet;
  • spend with a single send call.

Bitcoin Core drives both MuSig2 signing rounds. HWI first returns the Ledger's
public nonce and later its partial signature. HWI preserves the other
participants' MuSig2 fields while communicating with Ledger.

Registered Taproot policies may contain Miniscript expressions in tapscript
leaves. HWI recognizes known Miniscript fragment and wrapper names there and
preserves the policy text while extracting the extended keys for BIP388. It
does not implement or compile Miniscript itself. This fallback is restricted
to tr() descriptors with tapscript leaves.

CI runs an end-to-end scenario against Bitcoin Core PR 91 and the Ledger
Bitcoin app 2.5.0 under Speculos. It covers:

  • descriptor registration;
  • address display;
  • a recovery leaf using and_v(v:pk(...),older(1));
  • funding;
  • both MuSig2 signing rounds;
  • broadcast and confirmation.

Rebuild instructions:

git fetch origin
git fetch sjors
git switch -c musig2 origin/master
git merge --no-ff sjors/2026/08/display-address-bip388
git merge --no-ff sjors/2025/07/policy-sign
git cherry-pick # the rest

MuSig2 support in this branch currently targets Ledger. Support for additional
hardware-wallet implementations can be added separately.

@Sjors

Sjors commented Jul 31, 2025

Copy link
Copy Markdown
Member Author

Cherry-picked the latest version of all the independent PRs. Other than displaying an address this should be all you need to use MuSig2.

@Sjors

Sjors commented Sep 5, 2025

Copy link
Copy Markdown
Member Author

Somehow I thought I already implemented sending, but it seems I didn't. Done now. Tested with key path as well as script path.

@Sjors

Sjors commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Did a complete overhaul to match Sjors/async-hwi#2 (though just Ledger, we'll leave ColdCard for a followup). Much vibed, but manually tested. I'll extract smaller patches as separate PR's later.

This PR is orthogonal to #819, however I would prefer to use the newer Ledger app version from it, since it contains various MuSig2 related fixes.

@achow101

Copy link
Copy Markdown
Member

Testing this out, I noticed that it seems like if the ledger is the last signer to add a pubnonce, and I pass that PSBT right back to signtx, nothing happens.

@Sjors

Sjors commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

Rebased and probably fixed the ledger is the last signer issue. I can't test this myself at the moment.

@Sjors

Sjors commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

I'll rebase this and redo the Bitcoin Core mega-branch, after we have conceptual agreement on #791, #792 and #841.

The sort in sortedmulti occurs only during script expansion. Keep the
pubkeys in the order that they were provided to the parser.
For BIP388 policies, we will need to know wht index of each key
expression, so keep track of them explicitly with a key expression index
stored in each PubkeyProvider.

This also mirrors what Bitcoin Core does for descriptors.
The derivation path should be parsed and validated for correctness, not
just copied directly into a pubkey provider.
get_bip388_template returns the descriptor as a BIP 388 Wallet
Descriptor Template string.

get_pubkey_providers returns all of the pubkey providers from the
descriptor, in the same order as the placeholders in the bip388
template. Callers can get the strings for the key information vector by
calling PubkeyProvider.to_string_no_deriv()
After a descriptor is registered, we want to return to the caller some
information about the registration. RegisteredDescriptor is a class that
contains the name, the descriptor, the device type, and any data that
the device returned in response to the registration. This class can be
de/serialized from/to a string.
Adds registerdescriptor CLI command and its handlers.

Adds register_descriptor to HardwareWalletClient with boilerplate
implementation.
Sjors and others added 12 commits August 6, 2026 09:57
Translate the registered policy into a BitBox02 script
configuration and display the selected receive or change address.
Co-authored-by: Codex (GPT-5.6 Sol) <noreply@openai.com>
Accept a serialized registered descriptor with signtx and dispatch policy signing through a dedicated device method.
Extend the generic descriptor registration test to fund, sign, and finalize a transaction spending from the registered policy.
Reconstruct the registered wallet policy and pass its registration HMAC when signing the PSBT.
HWI's Coldcard simulator patch stores multiple derivation paths per
fingerprint so one seed can represent several multisig cosigners.

get_xfp_paths() iterates fingerprint keys instead of path lists. This
breaks descriptor enrollment and later signing fails with "Unknown
multisig wallet". Iterate over the mapping values instead.
Sign with the existing PSBT flow after registerdescriptor has stored the policy on the device.
Sign with the existing PSBT flow after registerdescriptor has stored the policy on the device.
Move descriptor-to-policy translation into a reusable helper without changing registration behavior.
Translate the registered descriptor into the policy script configuration and use it for transaction inputs and change outputs.
@Sjors
Sjors force-pushed the musig2 branch 2 times, most recently from dec7507 to d1c3f0a Compare August 6, 2026 18:52
@Sjors

Sjors commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Rebased for the new approach. Github is currently broken, so I can't update the PR description.

I'm not sure what the best way to handle miniscript fragments is. For now I added TRMiniscriptDescriptor which blindly accepts any tap-leaf script with a known fragment.

@Sjors

Sjors commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

I dropped the temporary commit 1b4cd98 that used /** instead of <0;1>/*, given #842 (comment). If you have a previous registration you need to test, you can cherry-pick it. Otherwise, just register the policy again.

@Sjors
Sjors force-pushed the musig2 branch 2 times, most recently from d509360 to 037ee58 Compare August 7, 2026 07:45
Sjors and others added 8 commits August 7, 2026 10:01
Registered descriptor policies may contain Miniscript expressions in
Taproot script leaves. Preserve these expressions as text while parsing
their extended keys into the BIP388 key information vector.

Restrict this fallback to tr() descriptors and known Miniscript fragment
and wrapper names.
This adds support handling public nonces and partial signatures.
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
Exercise descriptor registration and address display with a Miniscript
recovery leaf in addition to the MuSig2 key path. Relax the Nano X
automation match for its truncated primary-path label.
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants