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

📦 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:
t ≥ ⌊n / 2⌋ + 1
Example: 2-of-3 Threshold
- âś…
node0 + node1 → signs successfully
- âś…
node1 + node2 → signs successfully
- ❌
node0 alone → not enough shares
This ensures:
- No single point of compromise
- Fault tolerance if some nodes go offline
- Configurable security by adjusting
t and n
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
t partial signatures are received, they are aggregated into a full signature.
Properties
- No single point of compromise: Keys are never fully assembled
- Byzantine-resilient: Only
t of n nodes 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 database
db_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 URL
consul.address: Consul server address
MPC Configuration
mpc_threshold: Threshold for multi-party computation
event_initiator_pubkey: Public key of the event initiator
max_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
Preview usage
Start nodes
$ mpcium start -n node0
$ mpcium start -n node1
$ mpcium start -n node2
Client Implementations
Client Usage
Mpcium supports flexible client authentication through a signer interface, allowing you to use either local keys or AWS KMS for signing operations.
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
mpcClient := client.NewMPCClient(client.Options{
NatsConn: natsConn,
Signer: localSigner,
})
// 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,
})
// ... 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_P256
Expected 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
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 1
The 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
--algorithm supports ed25519 and p256 (default: ed25519).
--encrypt stores 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 the public_key into authorization.authorizer_public_keys.
Testing
1. Unit tests
go test ./... -v
2. Integration tests
cd e2e
make test
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.