Files
zeekay cfaed2eb1d feat(precompile): enable-everything builder surface — drop strict-PQ refusal from verify-only precompiles
Public permissionless launch policy: enable basically every precompile for
builder convenience, ESPECIALLY wallet-curve VERIFY so users sign natively on
Lux from other chains (ed25519=Solana, sr25519=Polkadot, secp256r1=WebAuthn,
secp256k1/ecrecover=Ethereum). Disable ONLY actual security risks. Lux's own
consensus and identity stay PQ (quasar/p3q) — enforced in the consensus
layer, never by refusing an EVM verifier a dapp asked for.

Two layers, decomplected:
  - builder EVM precompile surface : enable-all-verify  (this change)
  - chain consensus / finality     : PQ-strict          (consensus module, untouched)

Removed the RefuseUnderStrictPQ gate from 17 verify-only / key-safe custom
precompiles: ed25519, sr25519, secp256r1, bls12381 (EIP-2537, x7 ops),
kzg4844 (EIP-4844), blake3, poseidon, pedersen, babyjubjub, pasta, ring,
vrf, hpke, curve25519, x25519, cggmp21, frost, and the classical SNARK
verifiers in zk (Groth16/PLONK/Halo2/KZG/IPA/range/batch/commitment).

zk fflonk (0x03) stays DISABLED — but on its OWN forge-bug mechanism
(ErrFflonkDisabled, returned at dispatch), NOT strict-PQ: verifyFflonk has a
nil-vk soundness hole that forges any statement. Security disable, fully
PQ-independent.

The RefuseUnderStrictPQ helper + ErrClassicalForbiddenInPQ + StrictPQReporter
had zero remaining code callers (evm uses a local structural interface) —
deleted contract/strict_pq.go and its test. Rewrote zk's gate test to assert
the new policy (classical ops enabled, fflonk disabled). Removed the now-
orphaned isPedersenCommitment; fixed stale comments referencing deleted symbols.

Build: full module green. Tests: contract + zk + all 17 edited packages pass.

NOTE: the STANDARD eth precompiles (ecrecover, p256Verify, sha256, ripemd160,
blake2f, bls12381, kzg) are still refused by LuxStrictPQ() in the evm plugin —
a follow-up commit flips that to Permissive so ecrecover (every Ethereum dapp)
works at launch.
2026-06-27 21:00:43 -07:00
..

CGGMP21 Threshold Signature Precompile

Overview

The CGGMP21 (Canetti-Gennaro-Goldfeder-Makriyannis-Peled 2021) precompile enables threshold ECDSA signature verification. CGGMP21 is the state-of-the-art threshold signature protocol with identifiable aborts and efficient key refresh.

Address: 0x020000000000000000000000000000000000000D

Features

  • Threshold ECDSA: Any t out of n parties can sign
  • Identifiable Aborts: Malicious parties can be detected
  • Key Refresh: Update shares without changing public key
  • Standard ECDSA: Compatible with Ethereum, Bitcoin, etc.
  • MPC Custody: Enterprise-grade multi-party custody

Algorithm

CGGMP21 is a threshold signature scheme where:

  • n total parties hold shares of an ECDSA private key
  • Any t parties can collaborate to produce a valid signature
  • The signature is standard ECDSA (indistinguishable from single-party)
  • Malicious parties causing failures can be identified

Key Properties

  • Identifiable Aborts: Unlike GG20, can identify malicious parties
  • Efficient Refresh: Proactive security without downtime
  • Non-Interactive DKG: Efficient distributed key generation
  • Presignature Support: Precompute signatures for faster online phase

Specifications

Input Format

Total size: 170 bytes (minimum)

Offset Size Field Description
0-3 4 bytes threshold Minimum signers required (t)
4-7 4 bytes totalSigners Total number of parties (n)
8-72 65 bytes publicKey Aggregated ECDSA public key (uncompressed)
73-104 32 bytes messageHash Keccak256 hash of message
105-169 65 bytes signature ECDSA signature (r || s || v)

Output Format

32 bytes: Boolean result as uint256

  • 0x0000...0001 = Valid signature
  • 0x0000...0000 = Invalid signature

Gas Costs

Operation Base Gas Per-Signer Gas
CGGMP21 Verify 75,000 10,000

Examples:

  • 2-of-3 threshold: 75,000 + (3 × 10,000) = 105,000 gas
  • 3-of-5 threshold: 75,000 + (5 × 10,000) = 125,000 gas
  • 5-of-7 threshold: 75,000 + (7 × 10,000) = 145,000 gas
  • 10-of-15 threshold: 75,000 + (15 × 10,000) = 225,000 gas

Usage Examples

Solidity - Threshold Wallet

import "./ICGGMP21.sol";

contract MultiSigWallet is CGGMP21Verifier {
    bytes public thresholdPublicKey;
    uint32 public threshold;
    uint32 public totalSigners;

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

        verifyCGGMP21Signature(
            threshold,
            totalSigners,
            thresholdPublicKey,
            txHash,
            signature
        );

        // Execute transaction
        (bool success, ) = to.call{value: value}(data);
        require(success, "Execution failed");
    }
}

TypeScript (ethers.js)

const CGGMP21 = new ethers.Contract(
    '0x020000000000000000000000000000000000000D',
    [
        'function verify(uint32,uint32,bytes,bytes32,bytes) view returns(bool)'
    ],
    provider
);

// Verify 3-of-5 threshold ECDSA signature
const isValid = await CGGMP21.verify(
    3,              // threshold
    5,              // totalSigners
    publicKey,      // 65 bytes uncompressed
    messageHash,    // 32 bytes
    signature       // 65 bytes ECDSA
);

Go - MPC Integration

import (
    "github.com/luxfi/mpc/pkg/protocol/cggmp21"
)

// Initialize CGGMP21 protocol
protocol := cggmp21.NewProtocol()

// Distributed key generation (3-of-5)
keygenParty, err := protocol.KeyGen("party1", partyIDs, 3)
config := keygenParty.Result()

// Threshold signing
signParty, err := protocol.Sign(config, signers, messageHash)
signature := signParty.Result()

// Verify on-chain via precompile

Use Cases

1. Multi-Party Custody

contract InstitutionalCustody is CGGMP21Verifier {
    // 5-of-7 threshold for institutional custody
    uint32 constant THRESHOLD = 5;
    uint32 constant TOTAL_SIGNERS = 7;

    function transferAssets(
        address token,
        address to,
        uint256 amount,
        bytes calldata thresholdSig
    ) external {
        bytes32 txHash = keccak256(abi.encodePacked(token, to, amount));

        verifyCGGMP21Signature(
            THRESHOLD,
            TOTAL_SIGNERS,
            custodyPublicKey,
            txHash,
            thresholdSig
        );

        IERC20(token).transfer(to, amount);
    }
}

2. DAO Treasury Management

contract DAOTreasury is CGGMP21Verifier {
    // 7-of-10 DAO council
    function executeDAODecision(
        bytes32 proposalId,
        bytes calldata executionData,
        bytes calldata councilSignature
    ) external {
        verifyCGGMP21Signature(
            DAO_THRESHOLD,
            DAO_COUNCIL_SIZE,
            daoPublicKey,
            proposalId,
            councilSignature
        );

        // Execute DAO decision
    }
}

3. Cross-Chain Bridge

contract ThresholdBridge is CGGMP21Verifier {
    // Validators sign cross-chain messages
    function relayMessage(
        uint256 sourceChain,
        bytes calldata message,
        bytes calldata validatorSig
    ) external {
        bytes32 messageHash = keccak256(abi.encodePacked(
            sourceChain,
            block.chainid,
            message
        ));

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

        // Process cross-chain message
    }
}

Security Considerations

Threshold Selection

  • 2-of-3: Small teams, personal wallets
  • 3-of-5: Standard multi-sig, small DAOs
  • 5-of-7: Medium security, trading firms
  • 7-of-10 or higher: High security, institutional custody

Key Management

  • Key Refresh: Periodically refresh shares for proactive security
  • Backup Shares: Securely backup threshold shares
  • Identifiable Aborts: Monitor and remove malicious parties
  • Share Distribution: Never store all shares in one location

Message Hashing

Always hash messages with domain separation:

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

bytes32 messageHash = keccak256(abi.encodePacked(
    domainHash,
    nonce,
    data
));

Performance

Benchmarks on Apple M1 Max:

Configuration Gas Cost Verify Time Memory
2-of-3 105,000 ~65 μs 12 KB
3-of-5 125,000 ~80 μs 14 KB
5-of-7 145,000 ~95 μs 16 KB
10-of-15 225,000 ~140 μs 22 KB

Comparison with Other Schemes

Algorithm Type Signature Size Gas Cost Identifiable Aborts
CGGMP21 (this) ECDSA 65 bytes 75k-225k
FROST Schnorr 64 bytes 50k-125k
GG20 ECDSA 65 bytes 75k-225k
BLS (Warp) BLS12-381 96 bytes 120k

Integration with Lux MPC

This precompile integrates with /Users/z/work/lux/mpc/pkg/protocol/cggmp21:

import "github.com/luxfi/mpc/pkg/protocol/cggmp21"

// The MPC library provides:
// - protocol.KeyGen() - Distributed key generation
// - protocol.Sign() - Threshold signing
// - protocol.Refresh() - Share refreshing
// - protocol.PreSign() - Presignature generation

Standards Compliance

  • CGGMP21 Paper: ePrint 2021/060
  • ECDSA: secp256k1 curve (Bitcoin/Ethereum)
  • EIP-191: Signed data standard

References