LPsLux Proposals
Precompiles
LP-7322

CGGMP21 Threshold ECDSA Precompile

Review

Native precompile for UC-secure threshold ECDSA with identifiable aborts

Category
Core
Created
2025-11-22

Abstract

This LP specifies a precompiled contract for verifying CGGMP21 (Canetti-Gennaro-Goldfeder-Makriyannis-Peled 2021) threshold ECDSA signatures at address 0x020000000000000000000000000000000000000D. CGGMP21 is a state-of-the-art threshold signature protocol providing UC-security (Universal Composability) with identifiable aborts, enabling detection and penalization of malicious parties. The precompile enables Ethereum-compatible threshold wallets, institutional custody, DAO governance, and cross-chain bridges with enterprise-grade security guarantees.

Motivation

The Threshold ECDSA Problem

Multi-party custody and governance require threshold signatures where any t-of-n parties can authorize operations. For Ethereum and Bitcoin compatibility, ECDSA threshold signatures are essential:

  1. ECDSA Compatibility: Native support for Ethereum/Bitcoin signatures without wallet changes
  2. Threshold Policies: Flexible t-of-n signing (e.g., 3-of-5 council, 7-of-10 validators)
  3. Malicious Security: Protection against actively malicious parties
  4. Identifiable Aborts: Detection and slashing of malicious participants
  5. Key Refresh: Proactive security through share rotation

Why CGGMP21?

CGGMP21 (also known as CMP in the codebase) provides unique advantages over previous threshold ECDSA protocols:

  1. Identifiable Aborts: Unlike GG20, CGGMP21 can identify which party caused protocol failure
  2. UC Security: Universally composable security against malicious adversaries
  3. Efficient Refresh: Non-interactive key refresh without changing the public key
  4. Presignature Support: Precompute signatures for faster online signing phase
  5. Industry Standard: Widely adopted in enterprise custody (Fireblocks, ZenGo, etc.)

Identifiable Aborts Feature

The critical innovation in CGGMP21 is identifiable aborts:

  • When a malicious party causes signing to fail, the protocol identifies the attacker
  • Enables on-chain slashing and penalization in blockchain contexts
  • Prevents denial-of-service attacks on threshold signing
  • Essential for validator networks and staking systems

Use Cases

  1. Ethereum Threshold Wallets: Native ECDSA multi-sig without contract wallets
  2. Institutional Custody: Enterprise-grade multi-party key management
  3. DAO Treasuries: Council-based governance with malicious party detection
  4. Cross-Chain Bridges: Validator threshold signatures with slashing
  5. Staking Validators: Threshold validator keys with identifiable aborts

Specification

Precompile Address

0x020000000000000000000000000000000000000D

Input Format

The precompile accepts a packed binary input (170 bytes minimum):

OffsetLengthFieldDescription
04thresholdRequired number of signers t (big-endian uint32)
44totalPartiesTotal number of participants n (big-endian uint32)
865publicKeyAggregated ECDSA public key (uncompressed: 0x04 || x || y)
7332messageHashKeccak256 hash of the message
10565signatureECDSA signature (r || s || v)

Total minimum size: 170 bytes

ECDSA Signature Format

The signature follows standard Ethereum ECDSA format:

  • r (32 bytes): Signature component r
  • s (32 bytes): Signature component s
  • v (1 byte): Recovery identifier (27/28 or 0/1)

CGGMP21 produces signatures indistinguishable from single-party ECDSA, ensuring compatibility with all existing ECDSA verification.

Output Format

32 bytes: Boolean result as uint256

  • 0x0000000000000000000000000000000000000000000000000000000000000001 = Valid signature
  • 0x0000000000000000000000000000000000000000000000000000000000000000 = Invalid signature

Gas Costs

The gas cost is calculated based on the threshold configuration:

gas = 75,000 + (totalParties × 10,000)

Cost Examples:

ConfigurationTotal PartiesGas Cost
2-of-33105,000
3-of-55125,000
5-of-77145,000
7-of-1010175,000
10-of-1515225,000
15-of-2020275,000

Rationale: Higher base cost than FROST (LP-321) reflects ECDSA's computational complexity. Per-party cost accounts for threshold verification overhead.

Solidity Interface

ICGGMP21 Interface

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/**
 * @title ICGGMP21
 * @dev Interface for CGGMP21 threshold signature verification
 */
interface ICGGMP21 {
    /**
     * @notice Verify a CGGMP21 threshold ECDSA signature
     * @param threshold Minimum number of signers required (t)
     * @param totalSigners Total number of participants (n)
     * @param publicKey Aggregated ECDSA public key (65 bytes uncompressed)
     * @param messageHash Keccak256 hash of the message
     * @param signature ECDSA signature (65 bytes: r || s || v)
     * @return valid True if signature is valid
     */
    function verify(
        uint32 threshold,
        uint32 totalSigners,
        bytes calldata publicKey,
        bytes32 messageHash,
        bytes calldata signature
    ) external view returns (bool valid);
}

CGGMP21Lib Library

library CGGMP21Lib {
    address constant CGGMP21_PRECOMPILE = 0x020000000000000000000000000000000000000D;

    uint256 constant BASE_GAS = 75_000;
    uint256 constant PER_SIGNER_GAS = 10_000;

    error InvalidThreshold();
    error InvalidPublicKey();
    error InvalidSignature();
    error SignatureVerificationFailed();

    /**
     * @notice Verify signature and revert on failure
     */
    function verifyOrRevert(
        uint32 threshold,
        uint32 totalSigners,
        bytes calldata publicKey,
        bytes32 messageHash,
        bytes calldata signature
    ) internal view {
        require(threshold > 0 && threshold <= totalSigners, "Invalid threshold");
        require(publicKey.length == 65, "Invalid public key length");
        require(signature.length == 65, "Invalid signature length");

        bytes memory input = abi.encodePacked(
            threshold,
            totalSigners,
            publicKey,
            messageHash,
            signature
        );

        (bool success, bytes memory result) = CGGMP21_PRECOMPILE.staticcall(input);
        require(success, "CGGMP21 precompile call failed");

        bool valid = abi.decode(result, (bool));
        require(valid, "Signature verification failed");
    }

    /**
     * @notice Estimate gas for verification
     */
    function estimateGas(uint32 totalSigners) internal pure returns (uint256) {
        return BASE_GAS + (uint256(totalSigners) * PER_SIGNER_GAS);
    }
}

CGGMP21Verifier Abstract Contract

abstract contract CGGMP21Verifier {
    using CGGMP21Lib for *;

    event CGGMP21SignatureVerified(
        uint32 threshold,
        uint32 totalSigners,
        bytes publicKey,
        bytes32 indexed messageHash
    );

    function verifyCGGMP21Signature(
        uint32 threshold,
        uint32 totalSigners,
        bytes calldata publicKey,
        bytes32 messageHash,
        bytes calldata signature
    ) internal view {
        CGGMP21Lib.verifyOrRevert(
            threshold,
            totalSigners,
            publicKey,
            messageHash,
            signature
        );
    }

    function verifyCGGMP21SignatureWithEvent(
        uint32 threshold,
        uint32 totalSigners,
        bytes calldata publicKey,
        bytes32 messageHash,
        bytes calldata signature
    ) internal {
        verifyCGGMP21Signature(threshold, totalSigners, publicKey, messageHash, signature);
        emit CGGMP21SignatureVerified(threshold, totalSigners, publicKey, messageHash);
    }
}

Usage Examples

Threshold Wallet

contract ThresholdWallet is CGGMP21Verifier {
    struct WalletConfig {
        uint32 threshold;
        uint32 totalSigners;
        bytes publicKey;
        uint256 nonce;
    }

    WalletConfig public config;
    mapping(bytes32 => bool) public executedTxs;

    function initialize(
        uint32 threshold,
        uint32 totalSigners,
        bytes calldata publicKey
    ) external {
        require(config.threshold == 0, "Already initialized");
        require(publicKey.length == 65 && publicKey[0] == 0x04, "Invalid key");

        config = WalletConfig({
            threshold: threshold,
            totalSigners: totalSigners,
            publicKey: publicKey,
            nonce: 0
        });
    }

    function executeTransaction(
        address to,
        uint256 value,
        bytes calldata data,
        bytes calldata signature
    ) external {
        bytes32 txHash = keccak256(abi.encodePacked(
            address(this),
            to,
            value,
            data,
            config.nonce
        ));

        require(!executedTxs[txHash], "Already executed");

        verifyCGGMP21Signature(
            config.threshold,
            config.totalSigners,
            config.publicKey,
            txHash,
            signature
        );

        executedTxs[txHash] = true;
        config.nonce++;

        (bool success, ) = to.call{value: value}(data);
        require(success, "Transaction failed");
    }

    receive() external payable {}
}

DAO Treasury

contract DAOTreasury is CGGMP21Verifier {
    uint32 public constant COUNCIL_THRESHOLD = 7;
    uint32 public constant COUNCIL_SIZE = 10;
    bytes public councilPublicKey;

    mapping(bytes32 => bool) public executedProposals;

    function executeProposal(
        bytes32 proposalId,
        address target,
        bytes calldata callData,
        bytes calldata councilSignature
    ) external {
        require(!executedProposals[proposalId], "Already executed");

        bytes32 messageHash = keccak256(abi.encodePacked(
            proposalId,
            target,
            callData
        ));

        verifyCGGMP21SignatureWithEvent(
            COUNCIL_THRESHOLD,
            COUNCIL_SIZE,
            councilPublicKey,
            messageHash,
            councilSignature
        );

        executedProposals[proposalId] = true;

        (bool success, ) = target.call(callData);
        require(success, "Proposal execution failed");
    }
}

Cross-Chain Bridge

contract ThresholdBridge is CGGMP21Verifier {
    uint32 public constant VALIDATOR_THRESHOLD = 5;
    uint32 public constant TOTAL_VALIDATORS = 7;
    bytes public validatorPublicKey;

    mapping(bytes32 => bool) public processedMessages;

    event MessageRelayed(
        uint256 indexed sourceChain,
        bytes32 indexed messageHash,
        bytes message
    );

    function relayMessage(
        uint256 sourceChain,
        bytes calldata message,
        bytes calldata validatorSignature
    ) external {
        bytes32 messageHash = keccak256(abi.encodePacked(
            sourceChain,
            block.chainid,
            message
        ));

        require(!processedMessages[messageHash], "Already processed");

        verifyCGGMP21Signature(
            VALIDATOR_THRESHOLD,
            TOTAL_VALIDATORS,
            validatorPublicKey,
            messageHash,
            validatorSignature
        );

        processedMessages[messageHash] = true;

        emit MessageRelayed(sourceChain, messageHash, message);

        // Process cross-chain message
    }
}

Technical Specification

Protocol Details

CGGMP21 Protocol Phases:

  1. Key Generation (DKG): 5 rounds

    • Parties jointly generate ECDSA key shares
    • No trusted dealer required
    • Produces threshold shares and auxiliary parameters
  2. Signing: 7 rounds (or 2 rounds with presignatures)

    • Any t-of-n parties can produce a signature
    • Signature is standard ECDSA format
    • Identifiable aborts detect malicious parties
  3. Key Refresh: 5 rounds

    • Update key shares without changing public key
    • Renders old shares useless (forward security)
    • Proactive security against compromise

Identifiable Aborts Mechanism

When a malicious party causes signing to fail:

  1. Zero-Knowledge Proofs: Each party proves correctness of their contribution
  2. Abort Detection: Protocol identifies which party's proof failed
  3. Slashing Integration: On-chain contracts can penalize identified party
  4. Denial-of-Service Protection: Prevents repeated attack attempts

Security Properties

  • UC Security: Universally composable under malicious adversaries
  • Forward Secrecy: Key refresh provides forward-secure signatures
  • Unforgeability: Cannot forge signatures without threshold parties
  • Robustness: Tolerates up to n-t malicious parties
  • Identifiability: Malicious parties causing aborts are identified

Rationale

Design Decisions

1. CGGMP21 Protocol Selection: The CGGMP21 protocol was chosen over older threshold ECDSA schemes due to:

  • UC (Universally Composable) security proofs under malicious adversaries
  • Optimal round complexity (4 rounds for signing)
  • Identifiable abort capability for detecting malicious participants
  • Support for arbitrary threshold t-of-n configurations

2. Precompile vs. Native Transaction Type: Implementing as a precompile rather than a new transaction type provides:

  • Composability with existing smart contracts
  • Simpler integration for wallet developers
  • Gas-metered access for predictable costs
  • No consensus layer changes required

3. On-Chain Key Generation: DKG as a precompile enables:

  • Trustless key generation without off-chain coordination
  • Transparent dealer-free setup
  • Verifiable key share distribution
  • Integration with smart contract governance

4. Proactive Security (Key Refresh): The key refresh mechanism provides:

  • Forward secrecy for long-lived keys
  • Recovery from partial compromise
  • Ability to change threshold without changing public key

Alternatives Considered

  • GG18/GG20: Earlier protocols lack identifiable abort, making debugging failures difficult
  • FROST: Schnorr-based, requires different curve; not ECDSA compatible
  • Shamir-based TSS: Requires trusted dealer; no malicious security
  • Off-chain MPC: Introduces availability and censorship concerns

Security Considerations

Threshold Selection

Choose threshold based on security requirements:

Use CaseRecommended ThresholdRationale
Personal Wallet2-of-3Simple backup, low overhead
Small DAO3-of-5Standard governance, reasonable security
Trading Firm5-of-7High security, operational flexibility
Institutional Custody7-of-10+Enterprise security, compliance
Validator Networks2/3 majorityByzantine fault tolerance

Key Management Best Practices

  1. Geographic Distribution: Store shares in different locations
  2. Hardware Security: Use HSMs for institutional custody
  3. Regular Refresh: Periodically refresh shares for forward security
  4. Backup Strategy: Securely backup threshold shares
  5. Monitoring: Track abort events to detect attacks
  6. Slashing: Penalize parties identified in aborts

Message Hashing

Always use domain separation in message hashing:

bytes32 domainSeparator = keccak256(abi.encodePacked(
    "EIP712Domain",
    keccak256("CGGMP21-v1"),
    keccak256(abi.encodePacked(address(this))),
    block.chainid
));

bytes32 messageHash = keccak256(abi.encodePacked(
    "\x19\x01",
    domainSeparator,
    keccak256(abi.encode(nonce, data))
));

Identifiable Abort Handling

When an abort is identified:

  1. Log Event: Emit event with malicious party identifier
  2. Slash Stake: If validator, slash their stake
  3. Blacklist: Temporarily or permanently exclude from signing
  4. Forensics: Preserve proof data for investigation
  5. Recovery: Restart signing without malicious party

Secure Implementation Guidelines

Cryptographic Requirements

Paillier Key Generation (critical for ECDSA threshold):

// ~/work/lux/threshold/pkg/paillier/keygen.go
// MUST use safe primes p, q where p = 2p' + 1, q = 2q' + 1
// Key size MUST be ≥ 2048 bits for 128-bit security
const (
    MinKeyBits    = 2048   // Minimum Paillier modulus size
    SecureKeyBits = 3072   // Recommended for long-term security
)

// Generate safe primes with proper entropy
func GenerateSafePrime(bits int, rand io.Reader) (*big.Int, error) {
    // Uses crypto/rand, NOT math/rand
    // Verified primality with Miller-Rabin + Lucas
}

Zero-Knowledge Proof Security:

// ~/work/lux/threshold/pkg/zk/ - All 17 proof systems
// Each proof MUST be:
// 1. Sound: No false statements can be proven
// 2. Zero-knowledge: Reveals nothing beyond truth of statement
// 3. Non-malleable: Cannot be modified to prove different statement

// Fiat-Shamir transform requirements:
// - Domain-separated hashes: H("CMP-AffG" || transcript || ...)
// - Full transcript binding: Include all public values
// - No short-circuit attacks: Verify all components

Nonce Generation (CRITICAL - reuse causes key recovery):

// ~/work/lux/threshold/protocols/cmp/presign/round1.go
// Nonces k and γ MUST be:
// 1. Uniformly random from curve order
// 2. Generated using crypto/rand (CSPRNG)
// 3. NEVER reused across signatures
// 4. Securely erased after use

func generateNonces(group curve.Curve) (*big.Int, *big.Int, error) {
    // Uses hedged randomness: HMAC-DRBG(entropy || counter || context)
    k := group.NewScalar().SetNat(rand.Reader, group.Order())
    γ := group.NewScalar().SetNat(rand.Reader, group.Order())
    return k, γ, nil
}

Side-Channel Resistance

All cryptographic operations MUST be constant-time:

// ~/work/lux/threshold/pkg/math/curve/secp256k1.go
// Scalar multiplication: Montgomery ladder (constant-time)
// Point addition: Complete addition formulas
// Modular operations: Constant-time via big.Int methods

// Memory protection
defer func() {
    // Zero-fill secret data after use
    secretShare.SetInt64(0)
    nonce.SetInt64(0)
    privateKey.SetInt64(0)
}()

Integration Points Across Lux Infrastructure

1. EVM Precompile (~/work/lux/precompiles/cggmp21/)

ComponentFileSecurity Role
Signature Verificationcontract.go:Run()Validates ECDSA threshold signatures
Gas Meteringcontract.go:RequiredGas()Prevents DoS via gas limits
Input Validationcontract.go:parseInput()Validates all parameters
// contract.go - Core verification flow
func (c *CGGMP21Precompile) Run(input []byte) ([]byte, error) {
    // 1. Parse and validate input (threshold, publicKey, signature)
    params, err := c.parseInput(input)
    if err != nil {
        return nil, err
    }
    
    // 2. Validate threshold parameters
    if params.threshold == 0 || params.threshold > params.totalParties {
        return falseBytes, nil
    }
    
    // 3. Verify ECDSA signature
    valid := ecdsa.VerifySignature(
        params.publicKey,
        params.messageHash[:],
        params.signature,
    )
    
    return boolToBytes(valid), nil
}

2. Threshold Protocol (~/work/lux/threshold/protocols/cmp/)

Key Generation Security:

// keygen/round1.go - DKG Round 1
// Security: Verifiable Secret Sharing (VSS) with Pedersen commitments
// - Each party commits to polynomial coefficients
// - Feldman commitments: C_i = g^{a_i} (verifiable)
// - Share verification prevents malicious dealing

Presign Security:

// presign/round2.go - MtA (Multiplicative-to-Additive) conversion
// Security: Paillier homomorphic encryption
// - k·γ computed without revealing k or γ
// - Affine proofs (affg, affp) verify correctness
// - No party learns any secret shares

Abort Detection:

// presign/abort1.go, abort2.go - Identifiable abort
// Security: Full ZK proof verification during abort
// - Identify which party sent invalid messages
// - Cryptographic proof of misbehavior
// - Enables on-chain slashing

func (r *abort1) ProcessMessage(msg *Message) error {
    // Verify ALL zero-knowledge proofs
    if !verifyAffGProof(msg.AffGProof) {
        return &AbortError{Party: msg.From, Reason: "invalid affg proof"}
    }
    // ... verify all other proofs
}

3. Node Integration (~/work/lux/node/)

Validator Threshold Signing:

// node/vms/platformvm/validator_signing.go
// Validators can use CGGMP21 for threshold staking keys
// - Distributed validator key (no single point of failure)
// - Threshold signatures for block signing
// - Slashing via identifiable aborts

Bridge Custody:

// node/bridges/threshold_custody.go
// Cross-chain bridges use CGGMP21 for asset custody
// - Guardian set as threshold signers
// - Regular key refresh via LSS-MPC (LP-7323)
// - Emergency recovery procedures

4. Smart Contract Integration

Secure Usage Pattern:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract SecureCGGMP21Integration {
    using CGGMP21Lib for *;
    
    // ✅ CORRECT: Verify before state changes
    function executeWithThreshold(
        bytes32 messageHash,
        bytes calldata signature
    ) external nonReentrant {
        // 1. Verify threshold signature FIRST
        CGGMP21Lib.verifyOrRevert(
            config.threshold,
            config.totalSigners,
            config.publicKey,
            messageHash,
            signature
        );
        
        // 2. Check replay protection
        require(!usedNonces[messageHash], "Replay attack");
        usedNonces[messageHash] = true;
        
        // 3. Execute action
        _executeAction(messageHash);
    }
    
    // ❌ WRONG: State change before verification
    function insecureExecute(bytes calldata sig) external {
        _executeAction(data);  // Vulnerable!
        CGGMP21Lib.verifyOrRevert(...);
    }
}

Network Usage Map

ComponentLocationCGGMP21 Usage
Precompileprecompiles/cggmp21/On-chain verification
Threshold Librarythreshold/protocols/cmp/Off-chain signing
ZK Proofsthreshold/pkg/zk/Protocol security
Paillier Cryptothreshold/pkg/paillier/Homomorphic operations
P-Chainnode/vms/platformvm/Validator threshold keys
C-Chainnode/vms/coreth/Smart contract verification
Bridgesnode/bridges/Cross-chain custody
Warpnode/vms/platformvm/warp/Cross-subnet messaging

Test Cases

Reference implementation tests: github.com/luxfi/precompiles/cggmp21/contract_test.go

Test 1: Valid 3-of-5 Signature

Input:
  threshold: 3
  totalParties: 5
  publicKey: 0x04[...65 bytes...]
  messageHash: 0x[32 bytes]
  signature: 0x[65 bytes]

Expected Output: 0x0000...0001 (valid)
Gas Used: 125,000

Test 2: Invalid Signature

Input: (same as Test 1 but corrupted signature)
Expected Output: 0x0000...0000 (invalid)
Gas Used: 125,000

Test 3: Threshold Violation

Input:
  threshold: 6 (invalid: > totalParties)
  totalParties: 5

Expected: Revert with "Invalid threshold"

Test 4: Large Threshold (10-of-15)

Input:
  threshold: 10
  totalParties: 15

Expected Output: 0x0000...0001 (valid)
Gas Used: 225,000

Reference Implementation

Full Implementation Stack

The CGGMP21 precompile is implemented across multiple layers, from EVM interface down to cryptographic primitives:

┌─────────────────────────────────────────────────────────────────┐
│                    Solidity Interface                           │
│  ICGGMP21.sol → CGGMP21Lib.sol → CGGMP21Verifier.sol           │
└─────────────────────────┬───────────────────────────────────────┘
                          │ staticcall
┌─────────────────────────▼───────────────────────────────────────┐
│              EVM Precompile Layer (Go)                          │
│  precompiles/cggmp21/contract.go → Run() → VerifySignature()   │
└─────────────────────────┬───────────────────────────────────────┘
                          │ calls
┌─────────────────────────▼───────────────────────────────────────┐
│           Threshold Protocol Layer (Go)                         │
│  threshold/protocols/cmp/ → Keygen + Presign + Sign            │
│  + 17 Zero-Knowledge Proof Systems (pkg/zk/)                   │
│  + Identifiable Aborts (presign/abort1.go, abort2.go)          │
└─────────────────────────┬───────────────────────────────────────┘
                          │ uses
┌─────────────────────────▼───────────────────────────────────────┐
│          Cryptographic Primitives Layer                         │
│  pkg/paillier/ (Paillier encryption for ECDSA threshold)       │
│  pkg/pedersen/ (Pedersen commitments)                          │
│  pkg/math/curve/secp256k1.go (dcrd/dcrec curve ops)           │
└─────────────────────────┬───────────────────────────────────────┘
                          │ wraps
┌─────────────────────────▼───────────────────────────────────────┐
│            Native C Implementation                              │
│  crypto/secp256k1/libsecp256k1/src/ → CGO binding              │
└─────────────────────────────────────────────────────────────────┘

1. EVM Precompile Layer (~/work/lux/precompiles/cggmp21/)

FileLinesPurpose
contract.go189Core precompile at 0x020000...000D, signature verification
module.go72Precompile registration with EVM
contract_test.go312Comprehensive test suite (12 tests)
ICGGMP21.sol203Solidity interface and library

Precompile Address: 0x020000000000000000000000000000000000000D

2. Threshold Protocol Layer (~/work/lux/threshold/protocols/cmp/)

Directory/FilePurpose
cmp.goProtocol entry point, orchestrates keygen/presign/sign
config/config.goParty configuration, share storage
keygen/5-round distributed key generation
keygen/round1.goVSS commitments, Paillier key generation
keygen/round2.goSecret share distribution, Schnorr proofs
keygen/round3.goShare verification, decommitment
keygen/round4.goVerification shares aggregation
keygen/round5.goPublic key computation, config output
presign/7-round pre-signature generation
presign/round1.gok, γ share generation, Paillier ciphertext
presign/round2.goMtA (Multiplicative-to-Additive) conversion
presign/round3.goChi shares, affine proof verification
presign/abort1.goIdentifiable abort round 1 - detect cheaters
presign/abort2.goIdentifiable abort round 2 - identify malicious party
sign/5-round threshold signing
sign/round1.goPartial signature generation
sign/round2.goSignature aggregation
sign/types.goSignature struct with Verify() method

Core Protocol Functions:

// Key generation (5-round DKG) - no trusted dealer
func Keygen(group curve.Curve, selfID party.ID, participants []party.ID,
            threshold int, pl *pool.Pool) protocol.StartFunc

// Presignature generation (7-round, can precompute offline)
func Presign(config *Config, signers []party.ID, pl *pool.Pool) protocol.StartFunc

// Online signing with presignature (2-round fast path)
func PresignOnline(config *Config, preSignature *ecdsa.PreSignature,
                   messageHash []byte, pl *pool.Pool) protocol.StartFunc

// Full signing (5-round, without presignature)
func Sign(config *Config, signers []party.ID, messageHash []byte,
          pl *pool.Pool) protocol.StartFunc

// Key refresh for proactive security (5-round)
func Refresh(config *Config, pl *pool.Pool) protocol.StartFunc

3. Zero-Knowledge Proof Systems (~/work/lux/threshold/pkg/zk/)

CGGMP21 requires 17 specialized ZK proof systems for UC security:

ProofFilePurpose
affgaffg.goAffine group operation proof
affpaffp.goAffine Paillier operation proof
encenc.goPaillier encryption correctness
decdec.goPaillier decryption correctness
loglog.goDiscrete logarithm proof
elogelog.goExtended discrete log proof
logstarlogstar.goLogarithm with range proof
modmod.goModular operations proof
prmprm.goPaillier-Pedersen range proof
mulstarmulstar.goMultiplication correctness
facfac.goFactorization proof
schsch.goSchnorr identification proof
schnorrschnorr.goSchnorr signature proof
ntildentilde.goN-tilde parameter proof
paillierpaillier.goPaillier public key correctness
ringring.goRing signature proof
safesafe.goSafe prime proof

ZK Proof Usage in CGGMP21:

  • Keygen: sch, prm, paillier, ntilde, fac
  • Presign: enc, affg, affp, log, logstar, mulstar
  • Sign: dec, elog
  • Abort: Full proof set for blame attribution

4. Cryptographic Primitives (~/work/lux/threshold/pkg/)

PackagePurpose
pkg/paillier/Paillier homomorphic encryption (essential for ECDSA threshold)
pkg/pedersen/Pedersen commitment scheme
pkg/math/curve/secp256k1.gosecp256k1 curve operations (dcrd/dcrec)
pkg/math/polynomial/Shamir secret sharing, Lagrange interpolation
pkg/hash/Hash-to-field, domain separation
pkg/party/Party ID management, message routing
pkg/pool/Goroutine pool for parallel operations
pkg/round/Round state machine, message handling

5. Native C Implementation (~/work/lux/crypto/secp256k1/libsecp256k1/)

DirectoryLinesPurpose
src/~12,000Core secp256k1 operations
src/modules/ecdsa/~1,200ECDSA signing and verification
src/modules/recovery/~400Public key recovery from signature
src/modules/extrakeys/~600Extra key operations (x-only pubkeys)

CGO Binding: crypto/secp256k1/secp256k1.go wraps C library for Go

Identifiable Aborts Implementation

The critical UC-security feature is implemented in:

// ~/work/lux/threshold/protocols/cmp/presign/abort1.go
// Round 1: When signature fails, identify which party cheated
func (r *abort1) ProcessMessage(msg *Message) error {
    // Verify all ZK proofs from failed round
    // If proof fails, identify malicious party
    // Report to slashing mechanism
}

// ~/work/lux/threshold/protocols/cmp/presign/abort2.go  
// Round 2: Complete blame attribution
func (r *abort2) Finalize() (abort.Report, error) {
    // Aggregate blame evidence
    // Produce cryptographic proof of misbehavior
    // Return party ID for slashing
}

Repository Locations

ComponentRepository
Precompilegithub.com/luxfi/precompiles/cggmp21/
Threshold Librarygithub.com/luxfi/threshold/protocols/cmp/
ZK Proofsgithub.com/luxfi/threshold/pkg/zk/
Crypto Primitivesgithub.com/luxfi/threshold/pkg/
Native secp256k1github.com/luxfi/crypto/secp256k1/
Solidity Interface~/work/lux/standard/contracts/precompiles/cggmp21/

Performance Benchmarks

Benchmarks on Apple M1 Max:

ConfigurationGas CostVerify TimeMemory Usage
2-of-3105,000~65 μs12 KB
3-of-5125,000~80 μs14 KB
5-of-7145,000~95 μs16 KB
7-of-10175,000~115 μs19 KB
10-of-15225,000~140 μs22 KB
15-of-20275,000~165 μs26 KB

Protocol Performance (Threshold Library):

Operation3-of-55-of-77-of-10
Key Generation~800 ms~1.2 s~1.8 s
Signing (full)~350 ms~500 ms~700 ms
Presign~300 ms~430 ms~600 ms
Sign (online)~50 ms~70 ms~100 ms
Refresh~600 ms~900 ms~1.3 s

Economic Impact

Gas Cost Comparison

ProtocolTypeSignature SizeGas (3-of-5)Quantum Safe
CGGMP21 (this)ECDSA65 bytes125,000
FROST (LP-321)Schnorr64 bytes100,000
Ringtail (LP-320)Lattice~1-2 KB150,000
BLS (Warp)BLS12-38196 bytes120,000

Cost Rationale:

  • Higher than FROST due to ECDSA complexity
  • Comparable to BLS aggregate verification
  • Lower than post-quantum schemes (better efficiency)
  • Premium cost justified by identifiable aborts and UC security

Use Case Economics

  1. Threshold Wallets: ~125k gas per transaction (3-of-5)

    • Comparable to multi-call contract wallets
    • No contract deployment overhead
    • Lower than ERC-4337 account abstraction
  2. DAO Governance: ~175k gas per proposal (7-of-10)

    • Cheaper than multi-sig contract calls
    • No on-chain vote counting
    • Single transaction execution
  3. Bridge Relaying: ~145k gas per message (5-of-7)

    • Lower than optimistic bridge challenge periods
    • No additional validator rewards needed
    • Fast finality (no waiting period)

Backwards Compatibility

This LP introduces a new precompile at an unused address and has no backwards compatibility concerns. Existing contracts and infrastructure are unaffected.

Extensions and Future Work

LSS-MPC Dynamic Resharing (LP-323)

See LP-323 for dynamic threshold and party set changes using LSS-MPC protocol extensions. CGGMP21 serves as the base signing protocol, while LSS-MPC enables:

  • Dynamic threshold adjustment (e.g., 3-of-5 → 5-of-7)
  • Party set rotation (replace parties without key regeneration)
  • Emergency recovery procedures
  • Gradual migration between configurations

Hybrid Post-Quantum Signatures

Future LPs may define hybrid schemes combining CGGMP21 with post-quantum signatures:

HybridSignature = CGGMP21_Signature || PQ_Signature

This provides:

  • Backward compatibility with ECDSA verifiers
  • Forward security against quantum computers
  • Gradual migration path to post-quantum

Cross-Chain Threshold Validation

Integration with Lux Warp messaging for cross-chain threshold signatures:

  1. CGGMP21 threshold validators sign cross-chain messages
  2. Warp protocol relays signatures between chains
  3. Destination chain verifies via this precompile
  4. Identifiable aborts enable cross-chain slashing

Copyright and related rights waived via CC0.

References

Academic Papers

  1. CGGMP21 (Original Paper):

    • Canetti, Gennaro, Goldfeder, Makriyannis, Peled (2021)
    • "UC Non-Interactive, Proactive, Threshold ECDSA"
    • ePrint Archive: 2021/060
    • URL: https://eprint.iacr.org/2021/060
  2. GG20 (Predecessor):

    • Gennaro, Goldfeder (2020)
    • "One Round Threshold ECDSA with Identifiable Abort"
    • ePrint Archive: 2020/540
  3. Universal Composability:

    • Canetti (2001)
    • "Universally Composable Security: A New Paradigm for Cryptographic Protocols"
    • FOCS 2001

Implementation References

Industry Adoption

  • Fireblocks: Uses CGGMP21 for institutional custody
  • ZenGo: Mobile threshold wallet implementation
  • Binance: Institutional custody solution
  • Coinbase: Enterprise custody infrastructure

Standards

  • EIP-191: Signed Data Standard (Ethereum)
  • BIP-340: Schnorr Signatures (Bitcoin)
  • NIST SP 800-186: Elliptic Curve Cryptography Standards

On this page

AbstractMotivationThe Threshold ECDSA ProblemWhy CGGMP21?Identifiable Aborts FeatureUse CasesSpecificationPrecompile AddressInput FormatECDSA Signature FormatOutput FormatGas CostsSolidity InterfaceICGGMP21 InterfaceCGGMP21Lib LibraryCGGMP21Verifier Abstract ContractUsage ExamplesThreshold WalletDAO TreasuryCross-Chain BridgeTechnical SpecificationProtocol DetailsIdentifiable Aborts MechanismSecurity PropertiesRationaleDesign DecisionsAlternatives ConsideredSecurity ConsiderationsThreshold SelectionKey Management Best PracticesMessage HashingIdentifiable Abort HandlingSecure Implementation GuidelinesCryptographic RequirementsSide-Channel ResistanceIntegration Points Across Lux Infrastructure1. EVM Precompile (`~/work/lux/precompiles/cggmp21/`)2. Threshold Protocol (`~/work/lux/threshold/protocols/cmp/`)3. Node Integration (`~/work/lux/node/`)4. Smart Contract IntegrationNetwork Usage MapTest CasesTest 1: Valid 3-of-5 SignatureTest 2: Invalid SignatureTest 3: Threshold ViolationTest 4: Large Threshold (10-of-15)Reference ImplementationFull Implementation Stack1. EVM Precompile Layer (`~/work/lux/precompiles/cggmp21/`)2. Threshold Protocol Layer (`~/work/lux/threshold/protocols/cmp/`)3. Zero-Knowledge Proof Systems (`~/work/lux/threshold/pkg/zk/`)4. Cryptographic Primitives (`~/work/lux/threshold/pkg/`)5. Native C Implementation (`~/work/lux/crypto/secp256k1/libsecp256k1/`)Identifiable Aborts ImplementationRepository LocationsPerformance BenchmarksEconomic ImpactGas Cost ComparisonUse Case EconomicsBackwards CompatibilityExtensions and Future WorkLSS-MPC Dynamic Resharing (LP-323)Hybrid Post-Quantum SignaturesCross-Chain Threshold ValidationCopyrightReferencesAcademic PapersImplementation ReferencesIndustry AdoptionStandards