MuSig2 implementation is available in @scure/btc-signer/musig2.js. The process involves several rounds:
- Key Generation: Each signer generates a secret key and derives an individual public key using
musig2.IndividualPubkey(secretKey). - Key Aggregation: Signers share public keys. Use
musig2.sortKeys(publicKeys) to sort them, then musig2.keyAggregate(sortedKeys) to create the aggregate key. The final X-only aggregate public key is extracted via musig2.keyAggExport(...). - Nonce Generation (Round 1): Each signer generates nonces using
musig2.nonceGen(individualPubKey, secretKey, aggregatePubKey, msg). This returns both a secret (to be kept private) and a public nonce. - Nonce Aggregation: Signers share public nonces. Create a
musig2.Session by passing the aggregated public nonces via musig2.nonceAggregate(...), the sorted public keys, and the message. - Partial Signature (Round 2): Each signer generates a partial signature using
session.sign(nonceSecret, secretKey). - Aggregation & Verification: Aggregate partial signatures using
session.partialSigAgg(partialSignatures) and verify the final signature using standard Schnorr verification.
CRITICAL SECURITY WARNING: Every MuSig2 secret nonce must be used for exactly one partial signature. Reusing a nonce in distinct sessions can reveal the signer's secret key. Session.sign() only zeroes the specific Uint8Array passed to it; you must ensure clones or database records of the nonce are not reused.
import * as btc from '@scure/btc-signer';
import * as musig2 from '@scure/btc-signer/musig2.js';
import { schnorr } from '@noble/curves/secp256k1.js';
// 1. Key Generation
const aliceSecretKey = btc.utils.randomPrivateKeyBytes();
const alicePublicKey = musig2.IndividualPubkey(aliceSecretKey);
const bobSecretKey = btc.utils.randomPrivateKeyBytes();
const bobPublicKey = musig2.IndividualPubkey(bobSecretKey);
// 2. Key Aggregation
const individualPublicKeys = [alicePublicKey, bobPublicKey];
const sortedPublicKeys = musig2.sortKeys(individualPublicKeys);
const aggregatePublicKey = musig2.keyAggExport(musig2.keyAggregate(sortedPublicKeys));
// 3. Nonce Generation
const msg = new Uint8Array(32).fill(5);
const aliceNonces = musig2.nonceGen(alicePublicKey, aliceSecretKey, aggregatePublicKey, msg);
const bobNonces = musig2.nonceGen(bobPublicKey, bobSecretKey, aggregatePublicKey, msg);
// 4. Session & Nonce Aggregation
const session = new musig2.Session(
musig2.nonceAggregate([aliceNonces.public, bobNonces.public]),
sortedPublicKeys,
msg
);
// 5. Partial Signature
const alicePartialSignature = session.sign(aliceNonces.secret, aliceSecretKey);
const bobPartialSignature = session.sign(bobNonces.secret, bobSecretKey);
// 6. Aggregation
const finalSignature = session.partialSigAgg([alicePartialSignature, bobPartialSignature]);
// 7. Verification
schnorr.verify(finalSignature, msg, aggregatePublicKey);