Overview
Encrypt to a cohort, decrypt only with quorum cooperation. Three primitive families — ElGamal, Paillier, RSA — each with single-party and threshold modes.
Threshold Decryption
The MPC SDK ships three threshold-decryption primitives for the cases where signing isn't enough — you need to encrypt a payload, distribute the key as Shamir shares, and require quorum cooperation to decrypt. Every primitive runs entirely client-side in your application; no Zafeguard agent is required for the cryptography itself.
The three primitives
| Class | Underlying cryptography | What it's good at |
|---|---|---|
ThresholdElgamal | EC ElGamal with AES-GCM hybrid | Encrypt arbitrary payloads to a cohort whose key was already created via a normal DKG ceremony. Reuses your EC key shares. |
ThresholdPaillier | Paillier with composite modulus | The only one of the three that supports additive homomorphism — add two ciphertexts under the same key without decrypting either. |
ThresholdRsa | RSA-OAEP-SHA256 | Standard RSA-OAEP encryption with two threshold modes: a simpler trusted-dealer flow and a zero-trust flow where the combiner never reconstructs the private exponent. |
Single-party vs threshold
Every primitive supports two modes:
- Single-party (trusted-holder) — one party holds the full secret. Encrypt and decrypt with a single keypair, no cohort involvement. Useful when the application already has a single trusted process that needs encryption-at-rest with an MPC-friendly key format.
- Threshold (cohort) — the secret is Shamir-split across
nholders. Anythreshold-many cooperate to decrypt; fewer cannot.
In threshold mode every primitive follows the same three-step flow:
- Encrypt under the cohort public key. Anyone with the public key can do this — no cohort involvement.
- Each holder publishes a partial decryption of the ciphertext using its own share.
- A combiner collects threshold-many partials and runs
combinelocally to recover the plaintext.
The combiner role lives entirely in the calling application — the agents only produce partial decryptions, never the plaintext.
When to use which scheme
The decision tree:
Need additive homomorphism (add ciphertexts without decrypting)?
├─ Yes → ThresholdPaillier
└─ No → already have EC key shares from a signing cohort?
├─ Yes → ThresholdElgamal (reuses the shares)
└─ No → ThresholdRsa (standard RSA-OAEP envelope)
Concrete examples:
- Confidential vote tallying — encrypt each vote under a shared key, sum the ciphertexts homomorphically, decrypt only the final tally. Paillier.
- Sealed-bid auctions — encrypt bids, only reveal after the deadline via quorum cooperation. Any of the three; ElGamal if you already have a DKG, Paillier if you want bid-sum analytics, RSA for standards compatibility.
- Encrypted backups gated on quorum recovery — RSA-OAEP envelope around an AES-encrypted payload, with the RSA key Shamir-shared across recovery holders.
Curve and key-size policy
| Scheme | Curves / key sizes supported |
|---|---|
| ElGamal | Secp256k1, P256, P384, P521 |
| Paillier | Composite modulus n = p · q; production keys use primeBits ≥ 1024 (so n ≥ 2048 bits) |
| RSA | Composite modulus n = p · q; production keys use primeBits ≥ 1024 (so n ≥ 2048 bits). Default public exponent is 65537. |
For Paillier and RSA the SDK exposes the per-prime bit size as an explicit argument on keygen. There's no keygenSecure() default because the only correct value (≥ 1024) has multi-second key-generation latency that would surprise callers — explicit is better than implicit.
Security properties
Every threshold mode in this section ships with these guarantees:
- Threshold gate: fewer than
thresholdpartials must NOT decrypt. Verified end-to-end across all three schemes. - Wire-format stability: ciphertexts and share blobs carry an explicit version byte; the format is locked against silent drift.
- Scheme isolation: every ciphertext and share carries a scheme tag; mixing primitives at the combine layer is rejected with a clear error.
- Tampering rejection: bit-flipping a ciphertext on the wire surfaces a clear error rather than silently decrypting to garbage. ElGamal uses AES-GCM authenticated encryption; RSA uses OAEP padding with a constant-time-style padding check.
The zero-trust variant of RSA (shamirShareDShoup) goes one step further: the combiner never reconstructs the private exponent, so even a malicious combiner colluding with threshold-many holders cannot mint forged decryptions of unrelated ciphertexts. See the RSA page for when this matters.
What's NOT in scope
These primitives cover client-side encryption / decryption. They do NOT:
- Manage the cohort distribution of share blobs over the network — the application orchestrates that.
- Replace signing (use the Embedded Agent or ClusterAgent surfaces for that).
- Do multi-party Paillier or RSA key generation — the keypair is always generated locally by a single trusted dealer who then distributes shares. The dealer can be your application backend, an HSM, or any provisioning system you trust to mint and immediately discard the source key material.
The decryption-via-agents helper ThresholdElgamal.decryptViaAgents is the one case where the SDK does drive the cohort directly — it parallel-fetches partial decryptions from a list of agents and combines locally. The Paillier and RSA equivalents land alongside non-EC share storage on the agent side and are not yet in this SDK.