How AWS KMS Integration Works for Production Signing in Mpcium
Mpcium delegates all signing operations to AWS KMS, keeping private keys inside the service while nodes only handle the P-256 public key, enabling zero-exposure production deployments.
AWS KMS integration for production signing is the recommended security model for the fystack/mpcium MPC wallet infrastructure. By externalizing cryptographic operations to KMS, nodes never possess signing material, eliminating key-exfiltration risks while maintaining full compatibility with the library's threshold signing workflows.
Why Use AWS KMS for Production Signing?
Production deployments benefit from three core security properties implemented in the KMSSigner:
- Zero private key exposure – The ECDSA P-256 private key remains inside AWS KMS. Nodes call the
SignAPI and receive only the raw signature bytes, forwarding them to the MPC protocol without ever seeing the key material. - IAM role-based authentication – When running on AWS compute (ECS, EC2, EKS), the AWS SDK automatically assumes the attached IAM role, removing the need for static credentials in configuration files or environment variables.
- Algorithm enforcement – The implementation strictly validates that the requested key type is
EventInitiatorKeyTypeP256, ensuring only ECDSA with P-256 and SHA-256 is used for all operations.
Core Architecture of the KMSSigner
The KMSSigner Struct
The KMSSigner struct in pkg/client/kms_signer.go:18-24 encapsulates the AWS SDK client and key metadata:
type KMSSigner struct {
keyType types.EventInitiatorKeyType
client *kms.Client
keyID string
publicKey []byte // cached hex-encoded public key
}
This design caches the public key after initial retrieval to avoid repeated KMS API calls during the node lifecycle.
Configuration with KMSSignerOptions
Configuration is handled through KMSSignerOptions defined at pkg/client/kms_signer.go:26-33:
type KMSSignerOptions struct {
Region string
KeyID string
EndpointURL string // optional: for LocalStack
AccessKeyID string // optional: static credentials
SecretAccessKey string // optional: static credentials
}
The options struct supports multiple deployment patterns—from production IAM roles to local development with static keys or LocalStack endpoints.
Initialization and Public Key Loading
The NewKMSSigner function at pkg/client/kms_signer.go:35-90 orchestrates setup:
- Validates that
keyTypeisEventInitiatorKeyTypeP256, returning an error for unsupported algorithms. - Constructs an AWS SDK configuration using
config.LoadDefaultConfig, which automatically resolves credentials from the environment, shared config, or IAM role. - Creates a
kms.Clientwith the loaded configuration. - Calls
loadPublicKeyto retrieve and cache the public key.
The loadPublicKey method at pkg/client/kms_signer.go:93-122 invokes kms:GetPublicKey, parses the DER-encoded response, validates the ECDSA P-256 curve, and stores the hex-encoded public key for subsequent access via the PublicKey() method.
Implementing AWS KMS Signing in Production
IAM Role-Based Authentication
For production workloads running on AWS infrastructure, omit static credentials from KMSSignerOptions. The AWS SDK's default credential chain automatically assumes the IAM role attached to the EC2 instance, ECS task, or EKS pod. Ensure the role's policy includes:
{
"Effect": "Allow",
"Action": [
"kms:Sign",
"kms:GetPublicKey"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/abcd1234-ef56-7890-abcd-1234567890ab"
}
The Signing Workflow
When the MPC protocol requires a signature, the Sign method at pkg/client/kms_signer.go:124-143 executes:
func (k *KMSSigner) Sign(msg []byte) ([]byte, error) {
input := &kms.SignInput{
KeyId: aws.String(k.keyID),
Message: msg,
MessageType: types.MessageTypeRaw,
SigningAlgorithm: types.SigningAlgorithmSpecEcdsaSha256,
}
result, err := k.client.Sign(context.TODO(), input)
if err != nil {
return nil, fmt.Errorf("KMS sign failed: %w", err)
}
return result.Signature, nil
}
This implementation sends the raw message to KMS, which returns the DER-encoded ECDSA signature. The signature is then fed into Mpcium's MPC workflow without the node ever handling private key material.
Retrieving the Public Key for Configuration
After initializing the signer, extract the public key to populate the node's configuration:
pubKeyHex, err := kmsSigner.PublicKey()
if err != nil {
logger.Fatal("Failed to get public key", err)
}
logger.Info("Configure event_initiator_pubkey", "key", pubKeyHex)
This hex-encoded value must be set in config.yaml as event_initiator_pubkey so that peer nodes can verify initiator signatures during the MPC protocol.
Development and Testing Alternatives
Static Credentials for Local Development
When running outside AWS infrastructure (e.g., local laptops), provide explicit credentials via KMSSignerOptions:
kmsSigner, err := client.NewKMSSigner(
types.EventInitiatorKeyTypeP256,
client.KMSSignerOptions{
Region: "us-east-1",
KeyID: "arn:aws:kms:us-east-1:123456789012:key/abcd1234-ef56-7890-abcd-1234567890ab",
AccessKeyID: "AKIAIOSFODNN7EXAMPLE",
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
},
)
Source: README local dev snippet – README.md:60-71
LocalStack Integration
For integration testing without AWS access, configure the signer to point at a LocalStack instance:
kmsSigner, err := client.NewKMSSigner(
types.EventInitiatorKeyTypeP256,
client.KMSSignerOptions{
Region: "us-east-1",
KeyID: "alias/my-test-key",
EndpointURL: "http://localhost:4566",
AccessKeyID: "test",
SecretAccessKey: "test",
},
)
Source: LocalStack example – examples/generate/kms/main.go:48-53
Summary
- Zero-exposure security: The
KMSSignerinpkg/client/kms_signer.goensures private keys never leave AWS KMS, with nodes only caching the P-256 public key. - Flexible authentication: Production deployments use IAM roles automatically, while development environments can use static credentials or LocalStack endpoints via
KMSSignerOptions. - Strict algorithm enforcement: The implementation validates
EventInitiatorKeyTypeP256at initialization and usesSigningAlgorithmSpecEcdsaSha256for all KMS operations. - Seamless MPC integration: After initialization, the signer exposes
PublicKey()for configuration andSign()for the MPC workflow, handling raw ECDSA signatures via the KMSSignAPI.
Frequently Asked Questions
How does Mpcium ensure the private key never leaves AWS KMS?
The KMSSigner implementation in pkg/client/kms_signer.go never retrieves private key material. The NewKMSSigner function only fetches the public key via loadPublicKey (lines 93-122), and the Sign method (lines 124-143) sends the message to the KMS Sign API, returning only the signature bytes. The private key remains inside the KMS hardware security modules.
What IAM permissions are required for the KMSSigner to function?
The IAM role or user associated with the node requires two specific KMS permissions: kms:Sign to request signatures and kms:GetPublicKey to retrieve the public key during initialization. These permissions should be scoped to the specific key ARN used in the KeyID field of KMSSignerOptions to follow the principle of least privilege.
Can I use the KMSSigner for local development without AWS credentials?
Yes, the KMSSignerOptions struct supports AccessKeyID and SecretAccessKey for static credential injection, allowing local development outside AWS infrastructure. Additionally, you can point the signer at a LocalStack instance by setting EndpointURL to http://localhost:4566 and providing dummy credentials, as demonstrated in examples/generate/kms/main.go lines 48-53.
Why does the KMSSigner only support P-256 keys?
The implementation enforces EventInitiatorKeyTypeP256 in NewKMSSigner (lines 35-90) to ensure compatibility with Mpcium's MPC protocol, which expects ECDSA signatures over the P-256 curve with SHA-256 hashing. This constraint is hardcoded in pkg/types/types.go and validated at runtime to prevent algorithm mismatches that could cause signature verification failures in the distributed signing workflow.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →