Quick start
A minimal end-to-end Embedded Agent integration in under 30 lines — create, sign, verify, export, restore.
Quick start
A working 2-of-2 wallet (device + one cloud agent) in one TypeScript file. Drop it into a script, set the env vars, run it. Every step from DKG through signature verification is real — no mocks.
Call await Mpc.init(licenseCert) once at process start, before constructing any handle. EmbeddedAgent and ClusterAgent both expose finalizeSignature / verifySignature — local-crypto methods that re-validate the license on every invocation. Skipping Mpc.init(...) lets signer.create() succeed but throws as soon as you try to combine or verify a signature.
import { Utils } from '@zafeguard/mpc-sdk';
import {
Curve,
EmbeddedAgent,
Mpc,
RecoveryKind,
Utils,
} from "@zafeguard/mpc-sdk";
const NODE_URL = process.env.MPC_NODE_URL ?? 'https://your-agent.example';
const API_KEY = process.env.MPC_NODE_API_KEY ?? 'your-api-key';
const LICENSE = process.env.ZAFEGUARD_LICENSE_CERT!;
async function main() {
// 0) Activate the SDK license once per process. Required before
// any handle that uses finalizeSignature / verifySignature
// (i.e. EmbeddedAgent and ClusterAgent).
await Mpc.init(LICENSE);
// 1) Construct — no network call, just validates config.
const signer = new EmbeddedAgent({
agents: [{ baseUrl: NODE_URL, apiKey: API_KEY }],
threshold: 2,
curve: Curve.Secp256k1,
});
// 2) Create — runs DKG with the agent. Omit `keyId` and the SDK
// auto-generates a UUID; surface it via `loaded.keyId` and
// persist it alongside the blob so `load()` can find the right
// record later. The `passphrase` seals the post-DKG state into
// `loaded.exportedBlob`, ready to persist in one round-trip.
const loaded = await signer.create({
recovery: { kind: RecoveryKind.Noop },
passphrase: 'cold-backup-passphrase', // derive from HSM in production
});
console.log('keyId:', loaded.keyId);
console.log('publicKey:', loaded.publicKey.toString('hex'));
console.log('exportedBlob:', loaded.exportedBlob?.length, 'bytes ready for persistence');
// 3) Sign — one call. `signMessage` runs `sign()` (cohort round-trip
// for partials) and `finalizeSignature()` (local combine) for
// you, returning the assembled signature directly. Use the split
// `loaded.sign(...)` + `Utils.finalizeSignature(...)` path when
// the two halves run on different hosts.
const messageHash = Utils.sha256(new TextEncoder().encode('hello embedded agent'));
const sig = await loaded.signMessage({ messageHash });
// 4) Verify locally — no agent involvement.
const ok = loaded.verifySignature({
messageHash,
signature: sig.compactRecovery,
});
if (!ok) throw new Error('signature did not verify');
console.log('signature verified');
// 5) Persist — `create()` already produced the sealed blob via
// `loaded.exportedBlob`. Persist anywhere (file, OS keychain,
// encrypted DB). The agent never sees this blob.
// `loaded.export(passphrase)` is available too — call it later
// to re-seal under a different passphrase, or to snapshot after
// minting presigs.
const blob = loaded.exportedBlob!;
// 6) Restore — same keyId, same passphrase, fresh process.
// Reloaded handle holds the same publicKey byte-for-byte.
const signer2 = new EmbeddedAgent({
agents: [{ baseUrl: NODE_URL, apiKey: API_KEY }],
threshold: 2,
curve: Curve.Secp256k1,
});
const restored = await signer2.load({
keyId: loaded.keyId,
blob,
passphrase: 'cold-backup-passphrase',
});
console.log(
'publicKey matches across handles:',
restored.publicKey.equals(loaded.publicKey),
);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});Run it:
MPC_NODE_URL=https://your-agent.example \
MPC_NODE_API_KEY=your-api-key \
ZAFEGUARD_LICENSE_CERT=<your-license-cert> \
npx ts-node embedded-agent-quickstart.tsYou should see three lines: the public key hex, "signature verified", and publicKey matches across handles: true.
loaded.exportedBlob is a Buffer — opaque, sealed ciphertext. Persisting it is your job. The SDK never touches the filesystem, the OS keychain, or cloud storage; it hands you the bytes and you decide where they go. In production, persist to your platform's secure store:
- iOS — Keychain, optionally hardware-sealed via
P256Plugin+HardwareWrappedStorage(see Storage). - Android — Keystore via the same
HardwareWrappedStorageshape. - Node desktop / server —
EncryptedFileStorage(the bundled passphrase-sealed file backend). - Cross-platform / tests —
InMemoryKeyStorage.
Losing the blob without a configured recovery clause means the wallet is unrecoverable, so wire storage early.
What this snippet doesn't cover
The five-method lifecycle above is the minimum integration. The other pages in this section build on it:
- More than one agent (institutional redundancy, multi-region wallets): pass more entries to
agent[]and pick athresholdother than 2. See Topology. - Lower per-sign latency (pre-mint presignatures so each
sign()runs a single cohort round-trip instead of two): mint a batch withmintPresignaturesand let subsequent signs drain it. See Presignature pool. - Cold backup beyond the passphrase-encrypted blob above (Shamir-split across N contacts, custodian-sealed RSA, paper recovery): swap
RecoveryKind.NoopforPassword/Social/Custodial. See Recovery. - Share rotation (rotate the cryptographic material on a defined cadence without changing the on-chain address):
loaded.reshare({ passphrase }). See Recovery.
Overview
The high-level facade in @zafeguard/mpc-sdk for the warm-cold wallet shape — one device share, N cloud-agent shares, threshold signing across the cohort, with an optional presignature pool that batches the heavier round.
Topology
Choose the cohort that fits your trust model — single-agent for the simplest 2-of-2 wallet, multi-agent for redundancy and threshold flexibility.