Files
zeekayandHanzo Dev 74806677e2 A proof whose challenge is the word "challenge" proves nothing
ProveKnowledge and VerifyKnowledge in the Paillier helper both computed the
Fiat-Shamir challenge as the bytes of the literal string "challenge". A
challenge that does not depend on the statement or the commitment is known to
the prover before it commits, and the verification equation g^z = e · c^chal
can then be satisfied for ANY ciphertext by choosing z first and solving for
e. It is not a weak proof of plaintext knowledge; it is not a proof, and
anything that trusted it would accept a forgery from a party that holds no
plaintext at all.

The comments said as much — "in practice, use Fiat-Shamir", "Simplified
verification. In production, implement full verification as per CGGMP21" —
but the names promise a proof, and a name is what a caller reads. Nothing in
the estate calls either function, and the five bridge watchers that import
this package import it blank, for its side effects. So the placeholder is
removed rather than annotated: dead code that offers a security property it
does not have is worse than no code, because the next caller will not read
the comment.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-05 14:39:00 -07:00
..
2026-08-20 20:20:09 -07:00

cggmp21

Threshold-ECDSA protocol scaffolding for the CGGMP21 construction (Canetti, Gennaro, Goldfeder, Makriyannis, Peled, 2021 — "UC Non-Interactive, Proactive, Threshold ECDSA with Identifiable Aborts", eprint 2021/060).

CGGMP21 is Lux's canonical native-MPC construction for production signing paths (see the bridge policy in luxfi/bridge#405: "Production stays pure native MPC (CGGMP21 / FROST via luxfi/mpc)"). This package holds the in-tree types + protocol-round skeleton used by higher-level threshold code; the fully-verified production signer lives at github.com/luxfi/threshold and consumes the same primitives.

Scope

This package provides:

  • Party / session state machines for the four-round CGGMP21 flow (Commit → Reveal → Multiply → Open).
  • A Paillier keypair + additively-homomorphic encryption used by the MtA (multiplicative-to-additive) share-conversion step, including a Schnorr-style ZK-proof-of-plaintext-knowledge skeleton.
  • ECDSA signature and identifiable-abort record types.
  • A VerifySignature helper for the final aggregated signature.

This package does not provide:

  • A production-ready Finalize — the current Finalize intentionally returns an error and delegates to github.com/luxfi/threshold for the full MtA reconciliation, consistency checks, and identifiable-abort proofs. Do not call this package's Finalize from signing paths.
  • The full CGGMP21 ZK proof suite. ProveKnowledge / VerifyKnowledge here are a simplified Schnorr-style sketch (with a non-Fiat-Shamir placeholder challenge) intended to illustrate shape; production code MUST use the proofs specified in the CGGMP21 paper (Πenc, Πlog*, Πaff-g, Πmul, Πdec, etc.).
  • Distributed key-generation networking. Party.KeyGen populates local share state only; a transport layer supplies peer shares.
  • Dispute resolution or on-chain glue — those live above this package in the bridge / threshold layers.

Public API

Verified against cggmp21.go and paillier.go at file time.

Core types

Symbol Purpose
Signature ECDSA (R, S) output.
Config Threshold t, total parties n, curve, round timeout.
Party Per-node state (secret share, Paillier keys, sessions).
SigningSession Per-session state across all four rounds.
ECPoint (X, Y) on the configured curve.
Round2Message, Round3Message, Round4Message Wire messages between rounds.
IdentifiableAbort Blame record for a misbehaving party.

Party lifecycle

Symbol Purpose
NewParty(id, index, cfg, log) Constructor. Generates a 2048-bit Paillier keypair.
(*Party).KeyGen(parties) Local DKG step — samples x_i, computes [x_i]G.
(*Party).InitiateSign(sessionID, message) Opens a signing session; hashes message with SHA-256.
(*Party).Round1_Commitment(sessionID) Samples k_i, γ_i; returns H(i, [γ_i]G).
(*Party).Round2_Reveal(sessionID, commitments) Reveals [γ_i]G after receiving peer commitments.
(*Party).Round3_Multiply(sessionID, reveals) Verifies commitments, computes δ_i = k_i·γ_i and χ_i = x_i·k_i.
(*Party).Round4_Open(sessionID, round3msgs) Broadcasts δ_i and collects peer [δ_j]G.
(*Party).Finalize(sessionID, round4msgs) Stub — returns an error; use luxfi/threshold.

Helpers

Symbol Purpose
VerifySignature(pubKey, message, sig) Verifies a completed threshold signature against a public key.
ScalarBaseMult(curve, k) Convenience wrapper returning an *ECPoint.

Paillier (in paillier.go)

Symbol Purpose
PaillierPublicKey, PaillierPrivateKey Keypair types.
GeneratePaillierKeyPair(bits) Samples two primes, returns keypair.
(*PaillierPublicKey).Encrypt(m) Standard Paillier encryption.
(*PaillierPrivateKey).Decrypt(c) Standard Paillier decryption.
(*PaillierPublicKey).Add(c1, c2) Homomorphic addition (c1·c2 mod n²).
(*PaillierPublicKey).Multiply(c, k) Homomorphic constant-multiplication (c^k mod n²).
L(x, n) Standard Paillier L(x) = (x−1)/n.
ZKProof, ProveKnowledge, VerifyKnowledge Schnorr-style plaintext-knowledge proof sketch — see caveat above.

Paillier usage

CGGMP21 uses Paillier as the additively-homomorphic backbone of the MtA conversion — every Party holds a private Paillier key and the peers' public keys, so a share k_i can be multiplied against a peer's γ_j under encryption and the result reshared additively. The keypair is generated eagerly inside NewParty at 2048 bits; the current tree does not gate that on a config option.

Security notes

  • Threshold model. t-of-n with the identifiable-abort variant from CGGMP21: any party deviating from the protocol can be cryptographically blamed via IdentifiableAbort; the honest majority does not lose their key material on abort.
  • No key extraction on abort. Aborts surface at message-level proof failure, not by revealing secret shares.
  • Curve. Configured through Config.Curve — production callers should pass secp256k1 (or the equivalent luxfi/crypto/secp256k1) and never mix curves across a session.
  • Randomness. All sampling uses crypto/rand; do not substitute a deterministic source for testing outside of dedicated test builds.

Operational constraints

Per the bridge signing policy (luxfi/bridge#405):

  • Non-custodial by default. Production signing paths must remain pure native MPC — CGGMP21 (this package + luxfi/threshold) for ECDSA and FROST (via luxfi/mpc) for Schnorr / Ed25519.
  • No cosigner path. Cosigner / MPC-provider fallback is explicitly opt-in and must not be reachable from the default bridge configuration.
  • Finalize is not wired. Any caller reaching this package's Finalize is a bug — route through luxfi/threshold.

References

  • Canetti, Gennaro, Goldfeder, Makriyannis, Peled. UC Non-Interactive, Proactive, Threshold ECDSA with Identifiable Aborts. CCS 2020 / eprint 2021/060. https://eprint.iacr.org/2021/060
  • Sibling packages in this repo:
    • ../threshold — production threshold-signing entrypoint that this package feeds.
    • ../bls — BLS threshold (separate construction, not ECDSA).
  • Ecosystem:
    • luxfi/mpc — hosts the FROST Schnorr/Ed25519 side of the native-MPC stack.
    • luxfi/bridge — canonical consumer; PR #405 sets the production-signing policy this package supports.