Mpcium is a high-performance, open-source Multi-Party Computation (MPC) engine for securely generating and managing cryptographic wallets across distributed nodes—without ever exposing the full private key.
At its cryptographic core, Mpcium integrates tss-lib, a production-grade threshold signature scheme library developed by Binance. It supports:
-
ECDSA (secp256k1): Bitcoin, Ethereum, BNB, Polygon, and EVM-compatible L2 chains
-
EdDSA (Ed25519): for Solana, Polkadot, Cardano, and other modern blockchains
Resources
- MPC nodes architecture: MPC Fundamental and MPCIUM architecture
- MPC clients:
📦 Dependencies Overview
| Dependency | Purpose |
|---|---|
| NATS | Lightweight and resilient messaging layer for coordinating MPC nodes in real time. Enables pub/sub communication even under partial failure. |
| Badger KV | High-performance embedded key-value store used for local encrypted storage of MPC key shares and session data. |
| Consul | Service discovery and health checking to allow nodes to dynamically find each other and maintain cluster integrity. |
| tss-lib | Cryptographic engine for threshold key generation and signing, supporting ECDSA and EdDSA (used in Bitcoin, Ethereum, Solana, etc). |
| age | Modern encryption tool used for secure key material storage and protection with password-based encryption. |
Threshold & Nodes
Mpcium uses a t-of-n threshold scheme to securely generate and sign with private keys.
n= total number of MPC nodes (key shares)t= minimum number of nodes required to sign
Only t out of n nodes need to participate — the full private key is never reconstructed.
To maintain security against compromised nodes, Mpcium enforces:
Example: 2-of-3 Threshold
- ✅
node0 + node1→ signs successfully - ✅
node1 + node2→ signs successfully - ❌
node0alone → not enough shares
This ensures:
- No single point of compromise
- Fault tolerance if some nodes go offline
- Configurable security by adjusting
tandn
Architecture
Overview
Each Mpcium node:
- Holds a key share in local AES-256 encrypted storage (via Badger KV)
- Participates in threshold signing using
tss-lib - Communicates over a resilient messaging layer using NATS
- Registers itself with Consul for service discovery and health checks
- Verifies incoming messages using Ed25519-based mutual authentication
Message Flow & Signature Verification
- A signing request is broadcast to the MPC cluster through NATS as an authenticated event. Each node verifies the sender's Ed25519 signature before processing the request.
- NATS broadcasts the request to the MPC nodes.
- Each participating node verifies:
- The signature of the sender (Ed25519)
- The authenticity of the message (non-replayable, unique session)
- If the node is healthy and within the quorum (
t), it:- Computes a partial signature using its share
- Publishes the result back via NATS
- Once
tpartial signatures are received, they are aggregated into a full signature.
Properties
- No single point of compromise: Keys are never fully assembled
- Byzantine-resilient: Only
tofnnodes are required to proceed - Scalable and pluggable: Easily expand the cluster or integrate additional tools
- Secure peer authentication: All inter-node messages are signed and verified using Ed25519
Configuration
The application uses a YAML configuration file (config.yaml) with the following key settings:
Database Configuration
badger_password: Password for encrypting the BadgerDB databasedb_path: Path where the database files are stored
Backup Configuration
backup_enabled: Enable/disable automatic backups (default: true)backup_period_seconds: How often to perform backups in seconds (default: 300)backup_dir: Directory where encrypted backups are stored
Network Configuration
nats.url: NATS server URLconsul.address: Consul server address
MPC Configuration
mpc_threshold: Threshold for multi-party computationevent_initiator_pubkey: Public key of the event initiatormax_concurrent_keygen: Maximum concurrent key generation operations
chain_code (REQUIRED)
- Required for Hierarchical Deterministic (HD) wallet functionality to derive child keys
- Must be a 32-byte hexadecimal string (64 characters)
- All nodes MUST use the exact same chain_code value
- Generate with:
openssl rand -hex 32 - See INSTALLATION.md for detailed setup instructions
Installation
- Local Development: For quick setup and testing, see INSTALLATION.md
- Production Deployment: For secure production deployment with systemd, see deployments/systemd/README.md
Preview usage
Start nodes
$ mpcium start -n node0 $ mpcium start -n node1 $ mpcium start -n node2
Client Implementations
- Go: Available in the
pkg/clientdirectory. Check theexamplesfolder for usage samples. - TypeScript: Available at github.com/fystack/mpcium-client-ts
Client Usage
Mpcium supports flexible client authentication through a signer interface, allowing you to use either local keys or AWS KMS for signing operations.
Client ID (Result Routing)
When multiple client instances connect to the same MPC cluster, each client must set a unique ClientID to avoid result routing conflicts. Without distinct client IDs, two clients requesting operations concurrently may race for the same result message, causing one client to receive the other's response.
ClientIDscopes the NATS consumer and result subject so each client only receives its own results.- Allowed characters: alphanumeric, hyphens, and underscores (e.g.
"backend-svc-1","mobile_api"). - If you only run a single client instance,
ClientIDcan be omitted (empty string).
Local Signer (Ed25519)
import ( "github.com/fystack/mpcium/pkg/client" "github.com/fystack/mpcium/pkg/event" "github.com/fystack/mpcium/pkg/types" "github.com/google/uuid" "github.com/nats-io/nats.go" ) func main() { // Connect to NATS natsConn, err := nats.Connect(natsURL) if err != nil { logger.Fatal("Failed to connect to NATS", err) } defer natsConn.Close() // Create local signer with Ed25519 key localSigner, err := client.NewLocalSigner(types.EventInitiatorKeyTypeEd25519, client.LocalSignerOptions{ KeyPath: "./event_initiator.key", }) if err != nil { logger.Fatal("Failed to create local signer", err) } // Create MPC client with signer and a unique client ID mpcClient := client.NewMPCClient(client.Options{ NatsConn: natsConn, Signer: localSigner, ClientID: "backend-svc-1", // unique per client instance }) // Handle wallet creation results err = mpcClient.OnWalletCreationResult(func(event event.KeygenResultEvent) { logger.Info("Received wallet creation result", "event", event) }) if err != nil { logger.Fatal("Failed to subscribe to wallet-creation results", err) } // Create a wallet walletID := uuid.New().String() if err := mpcClient.CreateWallet(walletID); err != nil { logger.Fatal("CreateWallet failed", err) } logger.Info("CreateWallet sent, awaiting result...", "walletID", walletID) }
Local Signer (P256 with encrypted key)
// Create local signer with P256 key (encrypted with age) localSigner, err := client.NewLocalSigner(types.EventInitiatorKeyTypeP256, client.LocalSignerOptions{ KeyPath: "./event_initiator_p256.key.age", Encrypted: true, Password: "your-encryption-password", })
AWS KMS Signer
Production (IAM Role-based Authentication)
For production environments using IAM roles (recommended):
import ( "github.com/fystack/mpcium/pkg/client" "github.com/fystack/mpcium/pkg/types" ) func main() { // KMS signer with role-based authentication (no static credentials) kmsSigner, err := client.NewKMSSigner(types.EventInitiatorKeyTypeP256, client.KMSSignerOptions{ Region: "us-east-1", KeyID: "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", // No AccessKeyID/SecretAccessKey - uses IAM role }) if err != nil { logger.Fatal("Failed to create KMS signer", err) } mpcClient := client.NewMPCClient(client.Options{ NatsConn: natsConn, Signer: kmsSigner, ClientID: "kms-client-1", // unique per client instance }) // ... rest of the client code }
Development with Static Credentials
// KMS signer with static credentials (development only) kmsSigner, err := client.NewKMSSigner(types.EventInitiatorKeyTypeP256, client.KMSSignerOptions{ Region: "us-west-2", KeyID: "12345678-1234-1234-1234-123456789012", AccessKeyID: "AKIA...", SecretAccessKey: "...", })
LocalStack Development
// KMS signer with LocalStack for local development kmsSigner, err := client.NewKMSSigner(types.EventInitiatorKeyTypeP256, client.KMSSignerOptions{ Region: "us-east-1", KeyID: "48e76117-fd08-4dc0-bd10-b1c7d01de748", EndpointURL: "http://localhost:4566", // LocalStack endpoint AccessKeyID: "test", // LocalStack dummy credentials SecretAccessKey: "test", })
AWS Cloud Config Variations
// Different regions and key formats configs := []client.KMSSignerOptions{ // Key ID only { Region: "eu-west-1", KeyID: "12345678-1234-1234-1234-123456789012", }, // Full ARN { Region: "ap-southeast-1", KeyID: "arn:aws:kms:ap-southeast-1:123456789012:key/12345678-1234-1234-1234-123456789012", }, // Key alias { Region: "us-east-2", KeyID: "alias/mpcium-signing-key", }, }
Note: AWS KMS only supports P256 (ECDSA) keys, not Ed25519. If you need Ed25519, use the local signer.
Test with AWS KMS (LocalStack)
For local development and testing with AWS KMS functionality, you can use LocalStack to simulate AWS KMS services.
Setup LocalStack
-
Install and start LocalStack:
# Using Docker docker run -d \ -p 4566:4566 \ -p 4510-4559:4510-4559 \ localstack/localstack # Or using LocalStack CLI pip install localstack localstack start
-
Configure AWS CLI for LocalStack:
aws configure set aws_access_key_id test aws configure set aws_secret_access_key test aws configure set region us-east-1
Create P256 Key in LocalStack
-
Create a P256 keypair in AWS KMS:
aws kms create-key \ --endpoint-url=http://localhost:4566 \ --description "Test P-256 keypair for Mpcium" \ --key-usage SIGN_VERIFY \ --customer-master-key-spec ECC_NIST_P256Expected response:
{ "KeyMetadata": { "AWSAccountId": "000000000000", "KeyId": "330a9df7-4fd9-4e86-bfc5-f360b4c4be39", "Arn": "arn:aws:kms:us-east-1:000000000000:key/330a9df7-4fd9-4e86-bfc5-f360b4c4be39", "CreationDate": "2025-08-28T16:42:18.487655+07:00", "Enabled": true, "Description": "Test P-256 keypair for Mpcium", "KeyUsage": "SIGN_VERIFY", "KeyState": "Enabled", "Origin": "AWS_KMS", "KeyManager": "CUSTOMER", "CustomerMasterKeySpec": "ECC_NIST_P256", "KeySpec": "ECC_NIST_P256", "SigningAlgorithms": ["ECDSA_SHA_256"], "MultiRegion": false } } -
Get the public key (save the KeyId from step 1):
export KMS_KEY_ID="330a9df7-4fd9-4e86-bfc5-f360b4c4be39" # Replace with your KeyId aws kms get-public-key \ --endpoint-url=http://localhost:4566 \ --key-id $KMS_KEY_ID \ --query PublicKey \ --output text | base64 -d | xxd -p -c 256
Expected response (hex-encoded public key):
3059301306072a8648ce3d020106082a8648ce3d030107034200042b7539fc51123c3ba53c71e244be71d2d3138cbed4909fa259b924b56c92148cadd410cf98b789269d7f672c3ba978e99fc1f01c87daee97292d3666357738fd
Configure Mpcium for LocalStack KMS
Update your config.yaml file with the KMS public key and algorithm:
# MPC Configuration mpc_threshold: 2 event_initiator_pubkey: "3059301306072a8648ce3d020106082a8648ce3d030107034200042b7539fc51123c3ba53c71e244be71d2d3138cbed4909fa259b924b56c92148cadd410cf98b789269d7f672c3ba978e99fc1f01c87daee97292d3666357738fd" event_initiator_algorithm: "p256" # Other configuration... nats: url: "nats://localhost:4222" consul: address: "localhost:8500"
Test KMS Integration
Run the KMS example:
# Run the KMS example directly
go run examples/generate/kms/main.go -n 1The example will:
- Connect to LocalStack KMS endpoint
- Load the P256 public key from KMS
- Use KMS for signing wallet creation events
- Generate wallets using the MPC cluster
Authorization (Optional)
Mpcium can enforce an extra authorization layer on top of the initiator signature. When enabled, every keygen, signing, or reshare request must include signatures from trusted authorizers, strengthening decentralization and operational control. Authorization is disabled by default—activate it by extending your config.yaml (or per-node configs) with the block below:
authorization: enabled: true required_authorizers: - authorizer1 - authorizer2 authorizer_public_keys: authorizer1: public_key: "4711ec2728feb66f223078140323e0947a70a5fa36615c21382c2a9bc9241524" algorithm: "ed25519" authorizer2: public_key: "33d5b5b3973c9bd46d782bc5488ea1840188234b0cbed66153b691caafe85385" algorithm: "p256"
enabled: toggle the feature without removing existing configuration.authorizer_public_keys: register every authorizer ID and algorithm. Ed25519 keys must be 32-byte hex strings. P256 keys must use the hex-encoded PKIX (DER) bytes generated by the CLI.required_authorizers: list the IDs that must sign every request. Leave empty to accept any subset of the registered authorizers.- When authorization is on, upstream clients must attach matching signatures in the request payload; missing or invalid entries cause the node to reject the operation.
Generating Authorizers
Use mpcium-cli to issue authorizer identities and keys. The command below writes a JSON identity file (containing the public_key to paste into config.yaml) and a private key file, optionally protected with Age:
mpcium generate-authorizer \ --name authorizer1 \ --algorithm p256 \ --output-dir ./authorizers \ --encrypt
--algorithmsupportsed25519andp256(default:ed25519).--encryptstores the private key as<name>.authorizer.key.age; omit it to write the plain hex key.- The identity JSON is saved as
<name>.authorizer.identity.json; copy thepublic_keyintoauthorization.authorizer_public_keys.
Testing
1. Unit tests
2. Integration tests
Benchmarking
Test MPC performance with the integrated benchmark tool:
Keygen Benchmark
# Test wallet creation mpcium-cli benchmark keygen 10 # With config and output file mpcium-cli benchmark --config config.yaml --output results.txt keygen 50
Signing Benchmark
# Test ECDSA signing mpcium-cli benchmark sign-ecdsa 100 wallet-id # Test EdDSA signing mpcium-cli benchmark sign-eddsa 100 wallet-id # With custom batch size and output mpcium-cli benchmark --config config.yaml --output signing-results.txt \ sign-ecdsa 1000 wallet-id --batch-size 20
Use --prompt-password for secure key password input and --help for all options.

