Skip to main content

Keyshare Export

Export reconstructs the full private key of an MPC wallet by combining the client's keyshare with the server's. Use it to move a wallet out of MPC — for example, into another wallet ecosystem.

Once the private key is exported, the MPC security guarantees are gone. The key exists in one place, on the client, and whoever holds it controls the wallet.

This is not the same operation as backup and recovery, which moves the client's encrypted keyshare in and out of storage and never reconstructs a private key.

Why it goes through auth-svc

The cloud node exposes its export endpoint without authentication. It has no notion of users — it holds keyshares indexed by key_id, and it hands the encrypted share to anyone who asks for one.

auth-svc mirrors each of the node's export URLs one-to-one and puts three checks in front of them:

  1. A valid JWT access token.
  2. The token's scope claim contains export:keyshare.
  3. The authenticated user owns the wallet named by key_id in the request body.

Only then does auth-svc forward the request to the node and relay the response. The exported share is encrypted end-to-end to a public key the client generates, so auth-svc never sees key material in the clear.

Endpoints

The path encodes the signature scheme. The request and response bodies are identical for all three.

SchemeEndpoint
ECDSAPOST /v3/ecdsa/export
EdDSAPOST /v3/eddsa/export
TaprootPOST /v3/taproot/export

All three require Authorization: Bearer <access_token> with the export:keyshare scope. See POST /v3/ecdsa/export for the full contract.

Configuration

auth-svc needs to know where the node is:

auth_svc/config/.env
PROXY_UPSTREAM_URL=http://localhost:8080

Point it at the duo server. The docker-compose stacks set this for you — docker-compose.all.yml uses http://duo-server:8080.

If PROXY_UPSTREAM_URL is unset, auth-svc starts with a warning and every export request fails with 100411.

auth-svc is the authenticated front door, not a firewall. The node's own export path stays open, so a client that can reach the node directly can skip every check above and export any key_id it can guess.

In production, the cloud node must not be reachable from the public internet on its export path. Terminate traffic at a reverse proxy that allows /v3/*/export only from auth-svc, or keep the node on a private network where auth-svc is its only ingress. The boilerplate's compose stacks do not do this for you — they put every service on one Docker network for local development.

Granting the scope

export:keyshare is a permission on your Auth0 API, granted like the other boilerplate scopes — see Auth0 setup. Because it authorizes handing over a full private key, treat it as the most sensitive scope in the set: grant it to the specific client that performs export rather than to every application, and request it only in the token that runs the export flow.

For an end-to-end run with the demo client, see Authenticated Keyshare Export.