MPC SDK

Zafeguard MPC SDK (@zafeguard/mpc-sdk) — run threshold-MPC key generation, signing, and recovery client-side. Two entry points: EmbeddedAgent for the warm-cold wallet shape and ClusterAgent for raw cluster access.

MPC SDK — @zafeguard/mpc-sdk

Zafeguard ships two complementary TypeScript SDKs:

PackageWhat it doesWhen to use
@zafeguard/caller-sdkTrigger workflows and components on the platform; results stream back over SSE. Zafeguard holds the MPC key shares server-side.Standard product integrations. The platform manages keys, you call workflows.
@zafeguard/mpc-sdkRun threshold-MPC key generation, signing, and recovery client-side. Your application is one of the MPC parties.Embedded wallets, institutional custodians, and anyone who needs to hold a share locally and never expose it to the platform.

This page covers the second package — the SDK that lets your app participate directly in MPC ceremonies.

npm install @zafeguard/mpc-sdk

The package ships pre-built native binaries for every major Node.js platform plus a WebAssembly fallback for everywhere else. The loader picks the right artifact for the user's OS, architecture, and libc — no toolchain required at install time.

Platform support

RuntimeBackendNotes
Node.js 18+Native binaryLinux (glibc + musl), macOS (x86_64 + arm64), Windows (x86_64). Used automatically when available.
BrowserWebAssemblyModern browsers (Chrome / Safari / Firefox latest). All cryptographic ops run in WASM; the SDK calls Web Crypto for envelope wrapping when present.
React Native / ExpoWebAssemblyBundled with the same WASM artifact as the browser path. Pair EmbeddedAgent with a HardwareWrappedStorage adapter (see storage docs) to seal the device share against the Secure Enclave (iOS) or Keystore / StrongBox (Android).
Cloudflare Workers / Deno / BunWebAssemblySame artifact as browser, no platform-specific code.

The public API is identical across all four — EmbeddedAgent, ClusterAgent, every helper. Code that works in Node.js works unchanged in the browser, just slower (WASM is ~5× the native path on the same hardware).


Two entry points

@zafeguard/mpc-sdk exposes exactly two public surfaces. Pick the one that matches your topology:

EmbeddedAgent — warm-cold wallet (device + cloud agents)

The high-level facade. One device share, N cloud-agent shares, threshold signing across the cohort. Five-method lifecycle: createsignexportloadreshare. Includes a presignature pool that batches the heavier presigning round so subsequent sign() calls run a single cohort round-trip.

import { EmbeddedAgent, Utils, Curve, RecoveryKind } from '@zafeguard/mpc-sdk';

const embeddedAgent = new EmbeddedAgent({
  agents: [{ baseUrl: 'https://node-1.example', apiKey: '...' }],
  threshold: 2,
  curve: Curve.Secp256k1,
});

// Create a fresh key share. The returned handle exposes both `keyId`
// and the sealed `exportedBlob` — persist the blob to your hardware-
// backed storage and reload it later with `embeddedAgent.load(...)`.
const keyShare = await embeddedAgent.create({
  keyId,
  recovery: { kind: RecoveryKind.Noop },
  passphrase,
});

// Sign a message. The cohort returns this device's partial alongside
// every peer partial it observed — combine them with `Utils.finalizeSignature`.
const { partials, derivationPath } = await keyShare.sign({ messageHash });
const signature = Utils.finalizeSignature({
  curve: Curve.Secp256k1,
  messageHash,
  partialSignatures: partials,
  publicKey: keyShare.publicKey,
});

Use it for: consumer wallets (mobile / browser), institutional embedded-agent setups, anything that wants self-custody with a Zafeguard agent as a co-signer.

→ Full documentation: Embedded Agent

ClusterAgent — raw cluster client

The lower-level surface. Direct HTTP client for a single MPC cluster node. Use this when you want server-side custody (no embedded device party) — pure cluster-managed keys for institutional hot wallets, custody backends, or workflow integrations that hold shares on Zafeguard infrastructure.

import { ClusterAgent } from '@zafeguard/mpc-sdk';

const agent = ClusterAgent.connect(host, port, apiKey);
const session = await agent.sessions.createKeyShare({ /* DKG params */ });
const partial = await agent.presignature.sign(presigId, { messageHashB64 });

ClusterAgent exposes the cluster's full ceremony surface (DKG, presigning, signing, reshare, key-share migration, sign-pack export for cold-wallet flows) plus the recovery primitives (exportRecoveryBundle, RecoveryBundle.recover) used to reconstruct a root private key off-line from sealed shards.

For institutional setups that combine both — a backend that holds some shares via ClusterAgent while end-user devices hold others via EmbeddedAgent — the two surfaces work side-by-side. The agent your EmbeddedAgent points at is the same agent your backend talks to with ClusterAgent.


Supported curves

Every major chain's signing scheme is supported via the Scheme facade. Pick the curve at DKG time and the same key share works for every signing operation on that curve.

CurveSigning schemeChains
Secp256k1ECDSAEthereum, Bitcoin (P2PKH/P2SH), Cosmos
Bip340 / Secp256k1SchnorrSchnorrBitcoin Taproot
Ed25519EdDSASolana, Cardano, Polkadot
Ed448EdDSASignal, high-security PKI
P256 · P384 · P521ECDSATLS, WebAuthn, HSMs
Sr25519Schnorr (Ristretto)Substrate / Polkadot
StarknetECDSA (Stark)StarkNet / Cairo
Mina (Pallas)Schnorr (Pasta)Mina Protocol
ZilliqaEC-SchnorrZilliqa

BIP-32 hierarchical derivation is built in for every secp256k1- and ed25519-family curve, so a single root key share can derive an unlimited number of per-user / per-purpose addresses without re-running DKG.


Threshold cohorts

A cohort is the fixed set of MPC parties — your device plus every Zafeguard agent — that hold key shares for a single wallet. The cohort is decided at create() (key generation) and never changes; reshare rotates the cryptographic material but the cohort membership (which parties hold a share) stays the same. The threshold t is how many of those parties must cooperate to produce a signature.

The SDK supports any t-of-n threshold layout. Common shapes:

CohortQuorumBest for
2-of-2both requiredSingle-device wallet — device + one agent as a 2FA-style co-signer.
2-of-3any twoConsumer wallet with one redundant agent — tolerates one agent down.
3-of-3all threeHigh-assurance signing — every party must agree.
2-of-4any two of three agentsCross-region setups where the device works against whichever agent is reachable.

The same DKG produces a single root public key regardless of which signing subset later participates — any quorum of ≥ t parties produces a signature that verifies against the root pubkey deterministically. Picking a threshold is a tradeoff between availability (how many parties must be online to sign) and security (how many parties an attacker must compromise to forge a signature). See the topology page for the trade-off table.


Recovery and migration

The SDK covers four distinct lifecycle events. The right tool differs per event:

EventToolReference
Process restart, same deviceloaded.export(passphrase)signer.load()Recovery — restart and device swap
Cold backup outside the export blobrecovery: { kind: RecoveryKind.Password | Social | Custodial, ... } on create()Recovery — sealed-bundle cold backup
Routine share rotationloaded.reshare({ passphrase })Recovery — share rotation
Emergency recovery — reconstruct the raw root private key off-cluster (MPC infra unreachable, migrate off MPC, regulator-mandated extraction)ClusterAgent.exportRecoveryBundle from a quorum of cohort agents → RecoveryBundle.finalizeRecoveryBundle.recover on an air-gapped deviceRecovery — emergency recovery

For air-gapped or device-keyholder signing flows, the loaded handle exposes a partial-ferry path: pre-mint presignature entries during a connected window, then at sign time call loaded.presignature.sign({ presignatureId, messageHash }) to consume one entry locally and produce this device's partial only — no cohort contact. An online coordinator runs ClusterAgent.presignature.sign against each cohort agent to collect the matching partials, then combines them via Utils.finalizeSignature(...). See air-gapped sign.


Threshold decryption

Beyond signing, the SDK ships three threshold-decryption primitives for the cases where you need quorum cooperation to decrypt a payload — confidential vote tallying, sealed-bid auctions, encrypted analytics, audit-traceable recovery.

ClassBest for
ThresholdElgamalReuses your existing EC key shares from a DKG cohort. AES-GCM hybrid; supports arbitrary-length payloads.
ThresholdPaillierAdditive homomorphism — add encrypted values without decrypting. The right pick for confidential analytics.
ThresholdRsaStandards-compatible RSA-OAEP. Two threshold modes: simpler trusted-dealer or zero-trust combiner-blind.

The overview page walks the decision tree for picking the right scheme.


Post-quantum signing

For long-term archival signatures, regulatory mandates, or defence-in-depth alongside classical EC signatures, the SDK exposes six FIPS-standardised post-quantum algorithms via the PostQuantum class.

FamilyAlgorithmsBest for
ML-DSA (FIPS 204, lattice-based)MlDsa44, MlDsa65, MlDsa87General-purpose. Smaller signatures, faster signing. MlDsa65 is the FIPS-recommended default.
SLH-DSA (FIPS 205, hash-based)SlhDsaSha2128f/192f/256fConservative threat models that need the hash-function-only security foundation. Larger signatures, slower signing.

Post-quantum signing is single-party — there's no threshold or MPC variant, as threshold-PQ is an open research area with no standardised protocol yet. The recommended pattern during the transition is hybrid signing: classical threshold-MPC for your primary signing key, plus a single-party PQ signature for additional forward secrecy.


License requirement — Mpc.init(certificate)

Call Mpc.init(certificate) once at application startup, before any other SDK use. Every entry point in @zafeguard/mpc-sdk requires it:

Runtime + surfaceMpc.init(...) required?
Browser / React Native / Cloudflare Workers / Deno / Bun (any WASM runtime) — any surfaceRequired. Mpc.init() bootstraps the WASM binary in addition to verifying the license. Without it, every SDK call fails with "WASM not initialized in this browser/runtime. Call await Mpc.init(certificate) before creating a Scheme instance."
Node.js native — Scheme.* (any method)Required. Every public method on Scheme re-validates the license; defence-in-depth means patching the constructor alone does not bypass.
Node.js native — EmbeddedAgent / ClusterAgentRequired. Both surfaces expose finalizeSignature and verifySignature — local crypto operations that re-validate the license on every call. A handle whose process never called Mpc.init(...) succeeds at create() / connect() but throws as soon as you try to combine partials or verify a signature. Treat Mpc.init(...) as a mandatory app-boot step regardless of which class you reach for.

The cost is one async call at app boot; the benefit is one code path that works in every runtime and every surface.

import { Mpc, EmbeddedAgent, Scheme, Curve } from '@zafeguard/mpc-sdk';

await Mpc.init(process.env.ZAFEGUARD_LICENSE_CERT!);

// All subsequent SDK construction succeeds.
const signer = new EmbeddedAgent({ ... });
const scheme = new Scheme(Curve.Secp256k1);

Mpc.init writes the verified license into process-global state. Every EmbeddedAgent, ClusterAgent, Scheme, and Utils instance in the same process shares it — you don't init per-instance. The call is idempotent: a second Mpc.init(cert) on an already-initialised process returns the cached LicenseInfo without re-verifying, so it's safe to defensively call at every entry point that may run before main. There's also a lazy path: if you set the ZAFEGUARD_LICENSE_CERT environment variable, the first SDK call will read and initialise from it automatically. Calling Mpc.init explicitly is the safer pattern when you want a clear error at boot.

The Scheme license enforcement is what prevents the SDK's offline-crypto primitives from being used as building blocks to reverse-engineer a parallel MPC implementation. Licensed integrators see one init call at startup; unlicensed callers see a clear error at every entry point.

Where to start

  • Embedded Agent → — start here if your topology is "one device + Zafeguard agents over HTTPS." Concepts, quick-start, topology, presignature pool, recovery.
  • Embedded Agent quick-start → — minimal e2e example: create → sign → verify → export → load in under 30 lines.
  • Presignature pool → — pre-mint a batch to cut per-sign round-trips, opt-in auto-refill, pool export / import for cold-storage handoff, and the partial-ferry path for air-gapped signing.
  • Derivation paths → — how hierarchical key derivation flows through sign / finalise / verify, the standard layouts (BIP-32 / BIP-44), and the five rules to follow.

For deeper integration patterns, see the end-to-end recipes: Embedded Wallet Solution for in-app consumer wallets and Custody Whitelabel Solution for regulated hot- and cold-wallet stacks.


If your product wants Zafeguard to hold and manage the keys — the most common path — use @zafeguard/caller-sdk and call MPC components (SIGN_WITH_KEY_SHARE, GENERATE_KEY_SHARE, …) over the API.

If your product needs to hold a share itself — embedded wallets, institutional custodians, anyone who can't put trust in a single provider — use @zafeguard/mpc-sdk to participate in MPC ceremonies directly. The two SDKs are complementary and can be used together (e.g. caller-SDK for workflow orchestration, mpc-SDK for the signing party your product owns).

On this page