MuSig2 key aggregation
18. Threshold Signature Scripts
Overview
Threshold signatures achieve M-of-N spending policies where the resulting on-chain transaction looks like a single-signature spend — no revealing of participant keys, no indication of the threshold structure. This is distinct from OP_CHECKMULTISIG, which explicitly lists keys and signatures on-chain. Threshold signatures use cryptographic multi-party computation (MPC) to produce a single valid signature from a subset of participants.
Comparison: Script Multisig vs. Threshold Signatures
OP_CHECKMULTISIG (2-of-3):
On-chain reveals: 3 public keys, 2 signatures, multisig structure
Size: ~250 bytes for P2SH spend
Privacy: Low — structure is fully visible
Threshold signature (2-of-3):
On-chain reveals: 1 public key, 1 signature
Size: ~100 bytes (same as single-sig)
Privacy: High — indistinguishable from single-sig
FROST Protocol (Flexible Round-Optimized Schnorr Thresholds)
FROST is the leading Schnorr-based threshold signature scheme, enabling M-of-N Schnorr signatures with a distributed key generation (DKG) ceremony:
DKG Phase (one-time setup):
1. Each participant i generates a polynomial: f_i(x) = a_{i,0} + a_{i,1}x + ... + a_{i,t-1}x^{t-1}
2. Secret shares: s_{i,j} = f_i(j) for each other participant j
3. Public commitments: C_{i,k} = a_{i,k}·G
4. Participants verify received shares against commitments
5. Aggregate public key: Y = sum(a_{i,0}·G) for all i
Signing Phase (M of N participate):
1. Each signer i generates nonces: (d_i, e_i) and publishes (D_i, E_i) = (d_i·G, e_i·G)
2. Commitment aggregation: B = {(i, D_i, E_i)} for signing group
3. Binding factor: ρ_i = H_1(i, m, B)
4. Group nonce: R = sum(D_i + ρ_i·E_i) for all signers i
5. Challenge: c = H_2(R, Y, m)
6. Each partial sig: z_i = d_i + e_i·ρ_i + λ_i·s_i·c
where λ_i is the Lagrange coefficient for signer i
7. Aggregate: z = sum(z_i)
8. Final signature: (R, z) — a valid Schnorr signature!
MuSig2 for N-of-N (Full Quorum)
MuSig2 (BIP 327) enables N-of-N key aggregation and signing with a two-round interactive protocol:
# MuSig2 key aggregation
def musig2_aggregate_keys(pubkeys: list[bytes]) -> bytes:
# Compute aggregate key hash
L = b"".join(pubkeys)
H_agg = tagged_hash("KeyAgg list", L)
# Compute individual key coefficients
tweaked_keys = []
for pk in pubkeys:
a_i = tagged_hash("KeyAgg coefficient", H_agg + pk)
a_i_int = int.from_bytes(a_i, 'big') % SECP256K1_ORDER
P_i = decode_pubkey(pk)
tweaked_keys.append(ec_multiply(a_i_int, P_i))
# Aggregate
Q = ec_add_all(tweaked_keys)
return encode_x_only_pubkey(Q)
Taproot + FROST Integration
With P2TR, a FROST-managed wallet looks like this on-chain:
Locking script:
OP_1 <32-byte FROST aggregate public key>
Spending transaction:
scriptSig: empty
Witness: <64-byte FROST aggregate Schnorr signature>
No participant keys revealed. No threshold structure visible.
FROST signing ceremony happens entirely off-chain via MPC protocol.
TeachMeBitcoin is an ad-free, open-source educational repository curated by a passionate team of Bitcoin researchers and educators for public benefit. If you found our articles helpful, please consider supporting our hosting and ongoing content updates with a clean donation: