Skip to content

Authentication

XKOVA MPC does not use bearer tokens. You sign every request with an Ed25519 key. XKOVA stores only your public key, so it can verify your requests but can never produce them — there is no shared secret to leak from our side.

  1. You hold an Ed25519 seed (32 bytes). You register its public key.
  2. For each request, you compute a stamp — a signature over the timestamp, a nonce, the HTTP method, the path, and a hash of the body.
  3. The stamp travels in the X-Xkova-Stamp header. XKOVA verifies it against your registered public key.

Because the signature covers the method, path, and body, a captured request cannot be replayed against a different endpoint or with altered contents, and the timestamp and nonce bound its lifetime.

The client stamps every call for you — you only supply the seed:

import { XkovaMpcClient } from "@xkova/mpc";
const xkova = new XkovaMpcClient({
baseUrl: "https://api.mpc-dev.xkova.com",
seedHex: process.env.XKOVA_API_SEED!,
});

If you are not on Node, build the stamp yourself. The SDK’s stamp() shows the exact construction; the header is a base64url envelope of { pubkey, ts, nonce, sig }, where sig signs ts ‖ nonce ‖ method ‖ path ‖ sha256(body).

import { keyPairFromSeed, stamp } from "@xkova/mpc";
const key = keyPairFromSeed(process.env.XKOVA_API_SEED!);
const path = "/v1/vaults";
const body = Buffer.from(JSON.stringify({ tier: "self", name: "treasury" }));
const res = await fetch("https://api.mpc-dev.xkova.com" + path, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Xkova-Stamp": stamp(key, "POST", path, body),
},
body,
});

The stamp signs the exact bytes you send, so serialize the body once and use that same buffer for both the signature and the request. path includes /v1.

Your first key is provisioned at onboarding. After that you can create more — one per service or environment, each with a label — so a leak is contained to one key you can revoke on its own:

import { generateKeyPair } from "@xkova/mpc";
const k = generateKeyPair();
await xkova.createApiKey({
public_key: k.publicKey.toString("hex"),
label: "ci-signer",
});
// Store k.seed securely — XKOVA only ever sees the public key.
console.log("seed:", k.seed.toString("hex"));

generateKeyPair runs entirely on your side; the seed never touches the network.

const { api_keys } = await xkova.listApiKeys();
await xkova.revokeApiKey("key_123");

Revocation is immediate — the next request stamped with that key is rejected 401. Revoke a key the moment you suspect it is exposed; it does not affect any vault or its funds, only the ability to sign requests.

  • The seed is a credential. Store it in a secrets manager, not in source.
  • It authorizes API requests. It is not a key to any funds — moving funds is additionally gated by policy, limits, and (hosted tier) an end-user token. See How it works.
  • Rotate by minting a new key, cutting over, then revoking the old one.