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 Sign API 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:

  1. Validates that keyType is EventInitiatorKeyTypeP256, returning an error for unsupported algorithms.
  2. Constructs an AWS SDK configuration using config.LoadDefaultConfig, which automatically resolves credentials from the environment, shared config, or IAM role.
  3. Creates a kms.Client with the loaded configuration.
  4. Calls loadPublicKey to 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 snippetREADME.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 exampleexamples/generate/kms/main.go:48-53

Summary

  • Zero-exposure security: The KMSSigner in pkg/client/kms_signer.go ensures 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 EventInitiatorKeyTypeP256 at initialization and uses SigningAlgorithmSpecEcdsaSha256 for all KMS operations.
  • Seamless MPC integration: After initialization, the signer exposes PublicKey() for configuration and Sign() for the MPC workflow, handling raw ECDSA signatures via the KMS Sign API.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →