Hanzo

Hanzo MPC

Threshold multi-party signing for digital asset custody — distributed key generation, t-of-n signing, and proactive share refresh with post-quantum consensus.

Hanzo MPC

Hanzo MPC is wallet infrastructure for enterprise digital asset custody. Private keys are never assembled in one place — they are split into shares held by independent nodes, and a configurable threshold of nodes must cooperate to produce a valid signature. Think of it as a self-hosted alternative to Fireblocks: you own the infrastructure, control the key shares, and operate under your own policies.

Custody APIhttps://mpc.hanzo.ai
API prefix/v1
AuthHanzo IAM (OIDC) bearer tokens
Default thresholdt-of-n, default t = ⌊n/2⌋ + 1
Sourcegithub.com/hanzoai/mpc

Key Properties

  • No single point of failure — no node ever holds a complete private key
  • Distributed key generation (DKG) — keys are generated collaboratively; no dealer ever sees the whole key
  • Configurable thresholds — any t-of-n scheme (2-of-3, 3-of-5, 5-of-9, …)
  • Proactive share refresh — rotate shares without changing the public key or any derived address
  • Identifiable aborts — a deviating participant is detected and attributed
  • Multi-chain — one key plane derives addresses for Bitcoin, Ethereum/EVM, Solana, and more
  • Encrypted at rest — key shares are stored AES-256-GCM encrypted; the encryption key is supplied by Hanzo KMS/HSM, never plaintext config
  • Post-quantum consensus — cluster state is managed by a private BFT chain with dual-certificate finality (Ed25519 + ML-DSA-65, FIPS 204)

Supported Protocols

Hanzo MPC builds on the luxfi/threshold engine. Four threshold schemes are in production:

ProtocolTypeCurveBest for
CGGMP21Threshold ECDSAsecp256k1Bitcoin, Ethereum, EVM chains
FROSTThreshold Schnorrsecp256k1 (BIP-340), Ristretto255Bitcoin Taproot, Ed25519/sr25519 chains
LSSLinear secret sharingsecp256k1Dynamic resharing, blinded signing
BLSThreshold aggregateBLS12-381Validator keys, aggregate signatures

Key types accepted by the API: secp256k1, ed25519, sr25519, bls.

Authentication

The custody API trusts tokens issued by Hanzo IAM. Present an IAM-issued JWT as a bearer token:

curl -H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
  https://mpc.hanzo.ai/v1/info

Services that already hold an external OIDC token can exchange it for an MPC session token (issuer must be on the allowlist):

curl -X POST https://mpc.hanzo.ai/v1/auth/oidc \
  -H "Content-Type: application/json" \
  -d '{ "token": "<external-oidc-access-token>" }'

Roles (owner, admin, signer, viewer) gate each route; signing additionally requires mpc:sign. Long-lived API keys are also supported for machine callers — store them in Hanzo KMS, never inline.

Quick Start

Signing is a three-step flow: create a wallet (DKG), open a signing session, then sign.

Create a wallet (DKG)

curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
  -H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "protocol": "cggmp21" }'

Response:

{
  "walletId": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
  "protocol": "cggmp21",
  "threshold": "2-of-3",
  "status": "active",
  "createdAt": "2026-06-26T10:30:00Z"
}

Open a signing session

curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets/$WALLET_ID/sessions \
  -H "Authorization: Bearer $IAM_ACCESS_TOKEN"

Returns a sessionId. Signing consumes a session, which makes each signature an explicit, auditable authorization.

Sign

curl -X POST https://mpc.hanzo.ai/v1/mpc/sign \
  -H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "walletId": "'$WALLET_ID'",
    "sessionId": "'$SESSION_ID'",
    "message": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "encoding": "hex"
  }'

Response (ECDSA):

{ "signature": "0x...", "r": "0x1a2b3c...", "s": "0x4d5e6f..." }

Core Endpoints

All under https://mpc.hanzo.ai/v1.

MethodPathPurpose
POST/v1/mpc/walletsCreate a wallet (DKG); { "protocol": "cggmp21" | "frost" }
GET/v1/wallets/{id}/addressesDerived chain addresses for a wallet
POST/v1/mpc/wallets/{id}/sessionsOpen a signing session
GET/v1/mpc/wallets/{id}/sessionsList sessions
POST/v1/mpc/signThreshold-sign a message (requires sessionId)
POST/v1/wallets/{id}/reshareProactive share refresh / change t-of-n
GET/v1/status · /v1/infoCluster status and capabilities
GET/healthzLiveness (no auth)

A single wallet derives addresses for many chains:

curl https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/addresses \
  -H "Authorization: Bearer $IAM_ACCESS_TOKEN"
{
  "ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
  "bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
  "solana": "7nYBzS3Yz5mY8Qm4iXqz1bWmHnf8QpHq3xWv6Yc9aBcD"
}

Proactive Share Refresh

Rotate the key shares without changing the public key or any on-chain address. Old shares become useless after a refresh, so a share compromised in a past epoch cannot be combined with current shares.

curl -X POST https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/reshare \
  -H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "new_threshold": 3, "new_participants": ["node-0","node-1","node-2","node-3","node-4"] }'

In-cluster Go client (NATS)

Inside the cluster, the canonical interface is an asynchronous Go client over NATS — every keygen/sign/reshare request is Ed25519-signed by an initiator key and answered with a result event:

import (
    "github.com/luxfi/mpc/pkg/client"
    "github.com/luxfi/mpc/pkg/event"
)

mpc := client.NewMPCClient(client.Options{NatsConn: nc, KeyPath: "./event_initiator.key"})

mpc.OnWalletCreationResult(func(e event.KeygenResultEvent) {
    // e.ECDSAPubKey (CGGMP21), e.EDDSAPubKey (FROST), …
})
mpc.CreateWallet(walletID)

The REST custody API above is a facade over this same backend.

Security Model

  • Key share isolation — each node runs in its own pod with a dedicated encrypted volume; no node holds enough to reconstruct a key.
  • Encrypted shares — shares are AES-256-GCM encrypted in an embedded key-value store; the encryption key comes from an HSM or Hanzo KMS (kms.hanzo.ai). In production, plaintext-password backends are rejected outright.
  • Signed messaging — every inter-node and initiator message is Ed25519-signed and verified before processing.
  • Zero-knowledge proofs verify correctness at each protocol stage; a deviating party is identified and an operation aborts with a signed blame proof.
  • Post-quantum cluster state — peers, keys, and wallets live in a private BFT chain with dual-certificate (Ed25519 + ML-DSA-65) threshold finality.

Next Steps

Complete REST API: wallets, sessions, signing, resharing

CGGMP21, FROST, LSS, and BLS threshold schemes

The private BFT chain and dual-certificate finality

Use MPC as a threshold signing backend for Hanzo KMS

How is this guide?

Last updated on

On this page