MuSig2 power - #91
Conversation
7b0dd9f to
f842cae
Compare
2ecd3b6 to
0f809dd
Compare
c524af5 to
66705f3
Compare
|
Fresh rebase after bitcoin#31244 landed. |
|
Added bitcoin#33135 and the latest change to bitcoin#33008 (storing one hmac record per policy and fingerprint combination). |
|
The The fixup commits reflect changes made to the original branches. I'll up the commit history occasionally when one of its dependencies lands. |
|
bitcoin#29675 landed! Waiting for bitcoin#29136 to get a rebase so I can update the stack here. |
c1959d3 to
a42a034
Compare
|
Rebased! bitcoin#32821 and bitcoin#33135 also landed. Down from 67 commits to 44. I haven't had a chance to manually retest the whole flow though. |
a42a034 to
25e66d9
Compare
|
Rebased after bitcoin#32471 landed. Down to 37 commits. |
25e66d9 to
b7bb56e
Compare
b7bb56e to
14c7fce
Compare
14c7fce to
018d066
Compare
018d066 to
cf24e00
Compare
|
Added #110 which (very roughly) implements BIP388 policy support for displaying addresses. |
389caf4 to
9676dcc
Compare
|
Added #111 for signing support. |
684b910 to
6268484
Compare
CWallet::AddWalletDescriptor created a plain DescriptorScriptPubKeyMan even when WALLET_FLAG_EXTERNAL_SIGNER was set, unlike the wallet-loading path. Create the external-signer variant immediately so newly imported descriptors can use registration, address display, and signing without requiring an unload/reload cycle.
2c9dd9b to
dd2de8f
Compare
|
Rebased and updated with the new approach taken by HWI. It shrinks the implementation a bit, because with bitcoin-core/HWI#842 HWI takes care of converting the descriptor to BIP388. For easier testing, I added a temporary commit that migrates wallet registrations made before today to the new format. |
dd2de8f to
2017804
Compare
2017804 to
821487b
Compare
Dropped, because it's useless due to the switch from |
Exercise descriptor registration and registered address display with a tr(musig(...)) wallet. The mock validates the opaque registration and the derived address, including rejection of an incorrect signer response.
Multi-round signing flows like MuSig2 take two passes: round 1 exchanges public nonces, round 2 produces partial signatures. A single FillPSBT(sign=true) call therefore can never complete a fresh MuSig2 spend -- the user has to feed the round-1 PSBT back into FillPSBT a second time. The 'send' RPC, the GUI's send-coins dialog, the GUI's PSBT-operations dialog, and the bumpfee path all suffer from this: each makes a single sign call and returns the round-1 PSBT to the user. Hoist the second-pass logic into CWallet::FillPSBT itself so every caller benefits without code duplication. After the first pass leaves the PSBT incomplete, do a structural check: every input that participates in a MuSig2 session must already have a complete set of pub nonces from every expected participant. If so, run the same pass again on a copy; on success adopt it, on failure preserve the round-1 result so the caller can ferry the PSBT out-of-band as before. Skipped for non-MuSig2 PSBTs (no participants entries) and for MuSig2 PSBTs where some cosigner's nonces are still missing, so we don't issue an unnecessary device-confirmation prompt. Note: PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS entries can't be paired to PSBT_IN_MUSIG2_PUB_NONCE entries by aggregate pubkey -- the former stores the pre-tweak aggregate while the latter stores the post-tweak one (after BIP32 derivations and the BIP86 taproot tweak). The structural check sidesteps that. Refactor the existing one-pass body into a fill_pass lambda so both rounds share it.
Drive registered MuSig2 signing through both nonce and partial-signature rounds, and cover structured external-signer failures. The mock delegates to real wallets to produce valid contributions so the result can be finalized and broadcast.
RunCommandParseJSON throws when the signer subprocess exits non-zero, for example after a device disconnect. Fold that exception into the same PSBTError::EXTERNAL_SIGNER_FAILED path used for a structured signer error, and cover both paths with the MuSig2 signer mock.
Treat a signer failure as soft when the local pass adds a MuSig2 pubnonce or partial signature. This preserves the matching secret nonce and lets a later walletprocesspsbt resume after the device returns. Keep failures hard when the local pass made no progress, avoiding silent no-op success.
Walks all BIP 32 key expressions inside a descriptor (including musig() participants and subdescriptors) and returns each (KeyOriginInfo, root CExtPubKey) pair. Lets callers map descriptor xpubs back to the master fingerprints they came from without having to round-trip through ExpandPrivate. Used in the next commit by importdescriptors to bind known wallet HD seeds to descriptor xpubs at import time.
Returns every depth-0 extended key the wallet knows the private key for, indexed by the seed's master fingerprint. Walks both active descriptors and unused(KEY) SPKMs added via addhdkey. Used in the next commit by importdescriptors to look up xprvs that match a descriptor's key origins.
When importing a descriptor into a wallet that has private keys enabled, walk the descriptor's key origins and bind any xprv the wallet already knows (via addhdkey or an active descriptor) by master fingerprint + path. Verifies the derived xpub matches the descriptor's root xpub before binding to guard against fingerprint collisions. This lets callers import e.g. tr(musig([fp_A/87h/1h/0h]xpub_A,[fp_B/87h/1h/0h]xpub_B)/<0;1>/*) into a wallet that holds B's xprv without manually splicing the xprv into the descriptor string. The previous workaround forced the caller to round-trip through derivehdkey private=true and substitute the xprv back in -- error-prone, and exposes raw xprvs to anywhere the descriptor string travels.
Cover single-key autobinding, a non-matching fingerprint, and a two-key tr(musig(...)) descriptor that completes both signing rounds. Update the external-signer MuSig2 tests to import xpub-only descriptors and rely on the same autobinding path.
Enumerate every connected signer for descriptor registration and signing, storing one opaque registration per fingerprint. Registered address display uses the first matching signer. Cover a two-device MuSig2 wallet that registers both devices and completes signing in one call.
821487b to
43a91af
Compare
Combines the following:
The MuSig2-specific commits integrate with
bitcoin-core/HWI#794.
It:
and change descriptors, replacing
/0/*and/1/*with/<0;1>/*;registerdescriptorcommand;
name and fingerprint;
PSBT.
HWI may translate the descriptor to a device-specific representation, such as
a BIP388 wallet policy for Ledger, but that is not exposed to Bitcoin Core.
The general workflow is:
tr(musig(...)/<0;1>/*)descriptor;bitcoin-cli -rpcwallet=<wallet> registerdescriptor "MuSig wallet";walletdisplayaddress;sendcall.Bitcoin Core performs both MuSig2 signing rounds in-process. It collects public
nonces, retries signing once the nonce set is complete, collects partial
signatures, aggregates the final Schnorr signature, and broadcasts.
Descriptor registration and registered signing are fanned out to all connected
external signers. This supports wallets whose MuSig2 participants live on
multiple hardware devices. Registrations can be inspected through
getwalletinfo.external_signer_registrations.If a signer fails after a local MuSig2 contribution was added, the returned
PSBT preserves that contribution so signing can be resumed later.
Rebuild instructions:
git switch -c 2025/06/musig2-power origin/master git merge --no-ff sjors/2025/06/gethdkey git merge --no-ff sjors/2025/07/smart-createwalletdescriptor git merge --no-ff sjors/2025/07/no_script_path git merge --no-ff sjors/2025/07/external-signer-relax git cherry-pick # the restTests:
The HWI end-to-end test also exercises this branch against the Ledger Bitcoin
app under Speculos: descriptor registration, address display, funding, both
MuSig2 signing rounds, broadcast, and confirmation.