ElementsProject/secp256k1-zkp#131 adds MuSig2 support to secp256k1-zkp, which is an experimental fork of secp256k1. I think it would be useful to have a (series of) draft pr(s) implementing support for this in Bitcoin Core. If only to make it easier to actually test MuSig2.
MuSig2 paper: “MuSig2: Simple Two-Round Schnorr Multi-Signatures” (https://eprint.iacr.org/2020/1261)
To simplify things a bit, I’m assuming a things for now:
- The Bitcoin Core wallet coordinates setup and also takes the initiative for proposing a new transaction
- Two communication rounds are needed for signing (no storing nonces in advance)
- ~Only key path spending (for script path spending we probably want miniscript support first anyway)~ (probably not an issue)
- Our wallet has private keys (after setup we import the musig descriptor and mark that as active)
One challenge is how to include secp256k1-zkp (I suppose a proof of concept PR can just swap out the git subtree).
Wallet setup
We obtain an xpub with origin info from ourselves and the other signers. Perhaps at the BIP 87 derivation of m/87'/0'/0'
.
Todo:
- #22341
- …
Pass them into secp256k1_musig_pubkey_agg
. Apparently it’s possible to aggregate an xpub in one go, instead of calling this function on each and every derived key. But there’s some caveats, perhaps @jonasnick can clarify:
- This results in a new xpub, which can be used just fine by a watch-only wallet (which doesn’t need to know MuSig2 was involved); but
- agg(xpubA, xpubB)/* != agg(xpubA/, xpubB/)
- what about the chain codes?
- do we need to track xpub and/or origin info for the other signers? (this seems practical in any case when dealing with a dumb external signer)
Todo:
- RPC or bitcoin-util / bitcoin-wallet command that takes xpub(s), etc and does this
- since we have to call libsecp functions, a Python script doesn’t seem like the right approach
- the result could be a fresh (public key) descriptor, that is either automatically or manually imported in the wallet
- add musig2 descriptor (which tracks our own xpub at minimum, as well as the aggregated xpub or/and the other xpubs)
Round one nonces
One nice aspect of MuSig2 is that the nonces required for round 1 can:
- be generated in advance
- be regenerated if a signer loses them
- support only 1 cosigner if that’s easier
So it’s tempting to collect a bunch of nonces during the setup. However this is very non-trivial, so let’s not for now… #23326 (comment)
Instead, we generate our nonce and request one from each participant. Probably the most practical way is to create a PSBT, put our first nonce in it, pass it to the next signer who reads ours and adds theirs. We then get the PSBT back from the last signer.
- add PSBT fields
Round two: signing
Perform step 1 and 2 of signing process on the nonces we have.
Generate our round 2 nonce with secp256k1_musig_nonce_gen
(step 4) and add those to the PSBT
Pass the PSBT to the other signer(s):
- if there’s only 1 co-signer they can generate their nonce and
secp256k1_musig_partial_sign
in one go, and give us the result - if there’s 2+ co-signers???
Add our partial signature:
-
secp256k1_musig_partial_sign
(bywalletprocesspsbt
?)
Generate full signature:
- have one or more of PSBT method(s) do
secp256k1_musig_partial_sig_agg
(does not need wallet)
Profit.
Updates 2021-10-20 18:48 UTC: forget about pre committing nonces for round 1, instead use two signing rounds: #23326 (comment)