Threshold Decryption

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

ClassUnderlying cryptographyWhat it's good at
ThresholdElgamalEC ElGamal with AES-GCM hybridEncrypt arbitrary payloads to a cohort whose key was already created via a normal DKG ceremony. Reuses your EC key shares.
ThresholdPaillierPaillier with composite modulusThe only one of the three that supports additive homomorphism — add two ciphertexts under the same key without decrypting either.
ThresholdRsaRSA-OAEP-SHA256Standard 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 n holders. Any threshold-many cooperate to decrypt; fewer cannot.

In threshold mode every primitive follows the same three-step flow:

  1. Encrypt under the cohort public key. Anyone with the public key can do this — no cohort involvement.
  2. Each holder publishes a partial decryption of the ciphertext using its own share.
  3. A combiner collects threshold-many partials and runs combine locally 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

SchemeCurves / key sizes supported
ElGamalSecp256k1, P256, P384, P521
PaillierComposite modulus n = p · q; production keys use primeBits ≥ 1024 (so n ≥ 2048 bits)
RSAComposite 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 threshold partials 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.

On this page