Skip to content

Security

Threshold Signature Schemes Explained: GG18, GG20, FROST, and MuSig2 for Engineers

GG18, GG20, FROST, and MuSig2 compared for engineers building MPC custody systems: protocol mechanics, round counts, the 2022 GG18 vulnerability, and when to use each.

10 min
#mpc #threshold-signatures #gg20 #frost #musig2 #cryptography #custody

Evaluating threshold signature protocols for custody and smart-contract infrastructure is one of the more technically dense exercises in applied cryptography. The academic literature is dense, the production implementations have had meaningful security vulnerabilities, and the marketing around “MPC wallets” often obscures which protocol is actually running. This post is the distillation of that evaluation across institutional custody and ICO platform contexts.

The core problem all threshold signature schemes solve: you want a group of N parties to be able to sign a message using a threshold of M parties (M ≤ N), without any party - individually or in any subset smaller than M - being able to learn the complete private key. The key is generated in distributed form and never assembled in one place.

Why This Is Hard

Standard ECDSA (the signature scheme used by Bitcoin, Ethereum, and most blockchains) was designed for a single signer. Converting it to a threshold scheme requires solving a series of cryptographic challenges:

  1. Secret sharing: how do you split a secret s into N shares such that any M shares reconstruct s but any M-1 shares reveal nothing? Shamir Secret Sharing (SSS) solves this cleanly with polynomial interpolation.

  2. The nonce problem: ECDSA requires a per-signature random nonce k. If any two signatures share the same k, the private key is exposed (this is how the PlayStation 3 was hacked in 2010). In a threshold setting, the nonce must be jointly generated by the threshold parties - without any party learning the complete nonce - using the same distributed approach as the key.

  3. The multiplication problem: ECDSA signing requires computing s = k^{-1} * (hash + r * x) mod n, where x is the private key and k is the nonce. This multiplication of secret values cannot be done with simple linear secret sharing - you need multiplicative-to-additive (MtA) conversion, which is expensive.

These challenges explain why threshold ECDSA took years of academic work to produce practical protocols.

GG18: The First Practical Threshold ECDSA

Gennaro-Goldfeder 2018 (GG18) was the first widely-deployed threshold ECDSA protocol. It introduced:

  • Paillier homomorphic encryption to handle the MtA problem: parties can compute an addition of their secret values without revealing them, using the homomorphic properties of Paillier ciphertexts
  • A 3-round signing protocol (including a key generation phase)
  • A range proof zero-knowledge proof system to prevent malicious parties from injecting malformed values

The 2022 vulnerability. In August 2022, researchers published a critical vulnerability in GG18 implementations: the range proofs (ZK proofs that prove a party’s values are within valid ranges) could be bypassed in multiple open-source implementations. An attacker who controlled one signing party could construct malformed values that passed the zero-knowledge proofs, allowing them to eventually extract the complete private key from the other parties through repeated signing operations.

Affected libraries as of August 2022:

  • tss-lib (Go): not patched at disclosure time; patched later (check your version)
  • multi-party-ecdsa (Rust): patched; verify you are on the post-August-2022 version
  • Fireblocks: patched within 30 days
  • Zengo’s TSS: patched
# If using tss-lib, check your version
grep 'bnb-chain/tss-lib' go.sum
# Should be v2.0.0 or later (post-vulnerability patch series)

Protocol round count (GG18):

  • Key generation: 5 rounds (one-time setup)
  • Signing: 3 rounds (per-signature)

At network latency of 50ms per round-trip between co-signers, GG18 signing takes ~150ms of network time minimum.

GG20: Security Fix and Performance Improvements

Gennaro-Goldfeder 2020 improves on GG18 in two ways:

  • Stronger zero-knowledge proofs: the range proofs are redesigned to be sound against the attack class that affected GG18. If you are deploying threshold ECDSA today, GG20 is the minimum-acceptable protocol version.
  • 2-round signing: signing protocol reduced from 3 rounds to 2 rounds for the standard case (3 rounds for certain configurations)
Protocol Round Count Comparison:

Protocol    Key Gen    Signing    Notes
──────────────────────────────────────────────────────────────────────────────
GG18        5 rounds   3 rounds   Vulnerable to ZK proof bypass - do not use
GG20        5 rounds   2 rounds   Standard for threshold ECDSA (SECP256K1)
FROST       3 rounds   1 round    Schnorr-based; native Bitcoin/Solana support
MuSig2      1 round    2 rounds   N-of-N only; most efficient for full quorum

The GG20 signing flow:

Round 1 (Commitment):

Each party i:
1. Samples random ki (local nonce share)
2. Computes Ki = ki * G (commitment)
3. Encrypts ki using Paillier (for MtA in round 2)
4. Broadcasts (Ki, Paillier(ki), range_proof)

Round 2 (MtA + Signature Share):

Each party i:
1. For each other party j: runs MtA with their ki and kj via Paillier
   Result: additive shares of k (without knowing full k)
2. Uses the additive k shares to compute signature share si
3. Broadcasts si

Signature Reconstruction:

Any party (or a combiner):
Sum the signature shares s = Σsi mod n
Verify the resulting (r, s) is a valid ECDSA signature under the public key

Implementation reference (Rust, post-vulnerability):

// Use the maintained fork of multi-party-ecdsa
// Check that you're using a version from after August 2022
[dependencies]
curv-kzen = { version = "0.10", features = ["secp256k1"] }
multi-party-ecdsa = { git = "https://github.com/ZenGo-X/multi-party-ecdsa", rev = "abc123" }
# Pin to a specific known-good commit; audit before upgrading

FROST: Schnorr-Native Threshold Signatures

FROST (Flexible Round-Optimized Schnorr Threshold signatures, Chelsea Komlo & Ian Goldberg 2020) is designed specifically for Schnorr signatures - which are simpler to make threshold than ECDSA because Schnorr’s nonce commitment is linear (does not require MtA).

FROST is natively compatible with:

  • Bitcoin (via Tapscript/Taproot, which uses Schnorr)
  • Solana (Ed25519, which is a Schnorr variant)
  • Ethereum via BLS-12-381 (different curve but same algebraic structure)

FROST signing is single-round:

Pre-processing (offline, in advance):

Each party pre-generates (d, e) binding nonce pairs
Publishes commitments (D = d*G, E = e*G) for multiple future signing rounds

Signing (online, single round):

1. Coordinator broadcasts message to sign + list of participating parties
2. Each party computes their signature share using the pre-committed nonces
3. Coordinator aggregates shares into a single Schnorr signature

Single-round online signing means FROST latency is bounded by a single network round-trip between coordinator and co-signers. At 50ms network latency, FROST signing is ~50ms vs ~100ms for GG20.

FROST is standardized in IRTF RFC 9591 (2024). If you are building a new system on Bitcoin or Solana and MPC is appropriate, FROST is the recommended choice.

MuSig2: The N-of-N Special Case

MuSig2 (Nick, Jonas, Ruffing, 2021) addresses the specific case where all N parties must sign (N-of-N threshold). It is significantly more efficient than FROST for this case because:

  • The key aggregation is simpler (no secret-sharing needed)
  • Signing is 2 rounds (vs FROST’s 1 online round + offline preprocessing)
  • The resulting signature is a single standard Schnorr signature, indistinguishable from a single-signer operation

MuSig2 is appropriate for: 2-of-2 co-signing (e.g., client + server for a custodial wallet), N-of-N board approval of treasury transactions, any scenario where all participants are always online and available.

If any participant can be absent and you still need to be able to sign, use FROST instead.

Choosing the Right Protocol

Use Case                                            Recommended Protocol
────────────────────────────────────────────────────────────────────────────────
New system, Bitcoin/Solana, threshold signing        FROST (RFC 9591)
Existing Ethereum infrastructure, threshold          GG20 (post-Aug 2022)
All N signers always available, Schnorr chain        MuSig2
Client-server 2-of-2 custody                         MuSig2
Fireblocks/Zengo-compatible (ECDSA secp256k1)        GG20 (vendor implements)
Do not use                                            GG18 (vulnerable)

Verification checklist before deploying any MPC library:

#!/usr/bin/env python3
"""
mpc_library_audit.py - Minimal checks before production MPC deployment
"""

checks = [
    {
        "check": "GG18 ZK proof vulnerability",
        "question": "Is this library GG18-based or GG20-based?",
        "requirement": "Must be GG20 or FROST. GG18 is vulnerable.",
        "how_to_verify": "Check commit history for 'range proof' fixes post-August 2022",
    },
    {
        "check": "Malicious signer abort handling",
        "question": "What happens if one signer sends malformed data during signing?",
        "requirement": "Protocol must detect and attribute the malicious signer",
        "how_to_verify": "Look for identifiable abort in the ZK proof verification code",
    },
    {
        "check": "Nonce reuse prevention",
        "question": "How does the library prevent nonce reuse across signing sessions?",
        "requirement": "Must use commit-reveal with binding to the message; no deterministic nonce",
        "how_to_verify": "Verify nonce generation uses fresh randomness per session",
    },
    {
        "check": "Timing side channel",
        "question": "Are scalar multiplication operations constant-time?",
        "requirement": "Must use constant-time elliptic curve operations",
        "how_to_verify": "Check that the underlying EC library uses montgomery ladder",
    },
]

for check in checks:
    print(f"[  ] {check['check']}: {check['question']}")
    print(f"     Requirement: {check['requirement']}")
    print()

How This Breaks in Production

Failure 1: Running GG18 in production. Multiple MPC wallet vendors were still running GG18 as of late 2022. If your vendor was using tss-lib’s GG18 implementation and had not patched, your keys were at risk from any malicious co-signer. The attack requires participating in multiple signing sessions, but an attacker who has compromised one signer node has that access. Fix: verify your vendor’s protocol version. If you are self-hosting, check your dependency against the August 2022 vulnerability disclosures.

Failure 2: Signing availability collapse with a threshold of N-1. Your 5-of-7 scheme has one node down for maintenance. A second node goes down due to a network issue. Now you have 5 nodes available - exactly the threshold. Everything is fine. Then a third goes down for an unrelated reason. Signing is now impossible: you have 4 nodes, threshold is 5. Your fund cannot execute transactions. Fix: size your threshold to floor(N * 0.6) - for N=7, threshold=4. This gives you tolerance for 3 node failures (7-3=4 ≥ 4). Never set threshold at N-1; a single node failure becomes a signing outage.

Failure 3: Key share backup not tested for recovery. You have 5 key shares distributed. You have backup procedures documented. You have never tested recovery. A fire destroys the data center hosting 2 of your shares. You attempt recovery: the backup decryption keys are stored in the KMS account of the data center you just lost. Recovery is impossible. Fix: full disaster recovery drills, quarterly. Each drill must successfully reconstruct the signing key from backup materials only, without access to any online system.

Failure 4: Paillier key size too small. Some GG20 implementations use 1024-bit Paillier keys for the MtA phase. Research has shown this may be insufficient for full security. Standard GG20 security analysis assumes 2048-bit Paillier keys. Fix: verify your implementation uses 2048-bit Paillier keys (or equivalent security). openssl pkey -text on the Paillier public keys in your MPC configuration should show 2048-bit or larger.

Failure 5: FROST nonce pre-processing exhausted. FROST relies on pre-generated commitment nonces for fast signing. If you process more signing sessions than you have pre-generated commitments, signing stalls. Fix: monitor pre-commitment inventory. Trigger regeneration when inventory drops below 2x expected peak daily signing volume.

Failure 6: Protocol version mismatch between signer nodes after rolling upgrade. You upgrade 3 of 5 signer nodes to a new version of the MPC library. The new version uses a different serialization format for the signing protocol messages. Signing attempts between upgraded and non-upgraded nodes fail silently (messages are ignored as malformed). Fix: coordinate signer node upgrades to be simultaneous or implement protocol versioning that allows old and new versions to communicate during a rolling upgrade window.


Related reading: MPC vs HSM vs Multisig covers where MPC fits in the broader custody architecture. Key Ceremonies and Quorum Approvals covers how the initial key generation ceremony is conducted.

Continue Reading

Enjoyed this?

Get one deep infrastructure insight per week.

Free forever. Unsubscribe anytime.

You're in. Check your inbox.