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]>
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
VerifySignaturehelper for the final aggregated signature.
This package does not provide:
- A production-ready
Finalize— the currentFinalizeintentionally returns an error and delegates togithub.com/luxfi/thresholdfor the full MtA reconciliation, consistency checks, and identifiable-abort proofs. Do not call this package'sFinalizefrom signing paths. - The full CGGMP21 ZK proof suite.
ProveKnowledge/VerifyKnowledgehere 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.KeyGenpopulates 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-nwith the identifiable-abort variant from CGGMP21: any party deviating from the protocol can be cryptographically blamed viaIdentifiableAbort; 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 passsecp256k1(or the equivalentluxfi/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 (vialuxfi/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.
Finalizeis not wired. Any caller reaching this package'sFinalizeis a bug — route throughluxfi/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.