Complete Lifecycle of a Key Generation Session in mpcium: From Client Request to Persisted Key

A key generation session in mpcium follows a ten-step pipeline that transforms a signed client request into a distributed threshold signature scheme (TSS) key, persisting the private material in a KV store and returning the public key to the client via NATS JetStream events.

The mpcium repository implements a multi-party computation (MPC) wallet system that uses threshold signature schemes for secure key management. Understanding the complete lifecycle of a key generation session is essential for developers integrating with this system, as it involves coordinated interactions between clients, brokers, consumers, and TSS peer nodes.

Phase 1: Initiating the Request from Client to Broker

Step 1: Client Builds and Signs the GenerateKeyMessage

The lifecycle begins when a client calls CreateWallet (internally routed through CreateWalletWithAuthorizers in pkg/client/client.go). This method constructs a GenerateKeyMessage containing the wallet ID and threshold parameters, signs the payload using the configured initiator key, and publishes it to the keygen JetStream stream.

// Simplified client initiation flow
mpC := client.NewMPCClient(client.Options{
    NatsConn: nc,
    Signer:   signer,
})
mpC.CreateWallet(walletID) // Signs and publishes GenerateKeyMessage

According to the mpcium source code, this implementation resides in pkg/client/client.go within lines 11-37, where the client establishes the NATS connection and initiator signature before transmission.

Step 2: Broker Routes to the KeygenConsumer

Once the message hits the NATS server, the keygenBroker—initialized in cmd/mpcium/main.go (lines 191-206)—routes the message to the KeygenConsumer subscribed to the mpc.keygen_request.* subject pattern. This broker acts as the traffic coordinator, ensuring that key generation requests reach the appropriate processing nodes.

Phase 2: Validation and Session Construction

Step 3: Consumer Validates Peer Readiness

The keygenConsumer.handleKeygenEvent method in pkg/eventconsumer/keygen_consumer.go (lines 41-52) receives the message and immediately validates the system state. It invokes peerRegistry.ArePeersReady to verify that all required peers are online and available. If the check fails, the consumer issues a NAK (negative acknowledgment) to retry later and reports an error, preventing session creation with insufficient participants.

Step 4: Node Creates the TSS Session

Upon validation, the consumer calls Node.CreateKeyGenSession defined in pkg/mpc/node.go (lines 81-104). The node selects its own party ID, constructs the complete party ID list based on the current peer set, and instantiates either an ECDSA or EDDSA session via newECDSAKeygenSession or newEDDSAKeygenSession factory functions. This factory pattern ensures the correct cryptographic curve parameters are loaded for the specific key type requested.

Step 5: Session Initialization with tss-lib

The session's Init method—found in pkg/mpc/ecdsa_keygen_session.go (lines 81-86) for ECDSA and pkg/mpc/eddsa_keygen_session.go (lines 69-75) for EdDSA—creates a tss-lib LocalParty instance. It configures the appropriate elliptic curve (ecdsa.EC256 for ECDSA, edwards.Ed25519 for EdDSA) along with pre-parameters, initializing the local state required for the distributed protocol.

Phase 3: Distributed Key Generation Execution

Step 6: Running the TSS Protocol with Peer Message Exchange

The GenerateKey method spawns the TSS party via party.Start() and enters a coordination loop. As implemented in pkg/mpc/ecdsa_keygen_session.go (lines 89-115) and the EdDSA equivalent (lines 77-115), this loop performs two critical functions:

  • Outbound message handling: Forwards TSS protocol messages via handleTssMessage, which encrypts and publishes them to peer-specific NATS topics
  • Completion monitoring: Waits for the local party's saveData on the endCh channel, signaling that the distributed computation has finished successfully

This phase represents the core cryptographic work, where peers exchange encrypted shares and zero-knowledge proofs to collaboratively compute the key without any single party learning the full private key.

Phase 4: Persistence and Cleanup

Step 7: Serializing and Storing Private Key Material

When saveData arrives on the channel, the session proceeds to persist the sensitive material. In pkg/mpc/ecdsa_keygen_session.go (lines 102-128) and the EdDSA variant (lines 94-112), the session:

  1. Serializes the private key data and stores it via kvstore.Put for secure, encrypted storage
  2. Creates a KeyInfo struct containing participants, threshold, and version metadata, saving it to keyinfoStore
  3. Extracts and encodes the public key, storing it in s.pubkeyBytes for later retrieval

This separation of concerns ensures that cryptographic material is never held in memory longer than necessary and is immediately persisted to durable storage.

Step 8: Session Teardown and Resource Cleanup

Following successful persistence, the session invokes s.Close() from pkg/mpc/session.go. This method cleans up active NATS subscriptions, clears internal state buffers, and invokes the supplied done() callback to signal the consumer that processing is complete. Proper teardown prevents resource leaks and ensures the node can accept new sessions immediately.

Phase 5: Result Publication and Client Delivery

Step 9: Publishing the KeygenResultEvent

After the session finishes, the consumer enqueues a KeygenResultEvent on the genKeyResultQueue (handled in pkg/eventconsumer/keygen_consumer.go, lines 155-169). This event contains the wallet ID, the generated public key bytes (ECDSA and/or EDDSA), and a success status code. The consumer then ACKs the original NATS message, removing it from the stream and confirming successful processing.

Step 10: Client Receives the Wallet Creation Result

Clients that previously registered via MPCClient.OnWalletCreationResult (implemented in pkg/client/client.go, lines 40-51) receive the KeygenResultEvent through their subscription callback. At this point, the wallet ID is fully operational for signing operations or key resharing, completing the lifecycle.

Summary

The lifecycle of a key generation session in mpcium comprises these distinct phases:

  • Client initiation: Signed GenerateKeyMessage published to JetStream via CreateWallet
  • Broker routing: keygenBroker directs traffic to the subscribed consumer
  • Validation: peerRegistry.ArePeersReady ensures sufficient peers are available before proceeding
  • Session fabrication: Node.CreateKeyGenSession builds ECDSA or EdDSA TSS sessions using tss-lib
  • Protocol execution: GenerateKey runs the distributed computation, exchanging encrypted peer messages
  • Persistence: Private key material serialized to KV store with metadata saved to keyinfoStore
  • Cleanup: session.Close() releases resources and invokes completion callbacks
  • Result delivery: KeygenResultEvent published and ACKed, notifying the client via OnWalletCreationResult

Frequently Asked Questions

What happens if peer nodes are not ready during key generation?

If peerRegistry.ArePeersReady returns false in pkg/eventconsumer/keygen_consumer.go, the consumer immediately NAKs the message and reports an error through the error handling pipeline. This negative acknowledgment triggers a redelivery attempt by NATS JetStream, ensuring the request retries once the required peers come online rather than failing permanently or creating an incomplete session.

How does mpcium support both ECDSA and EdDSA key generation?

The system uses a factory pattern in pkg/mpc/node.go where CreateKeyGenSession accepts a session type parameter. For SessionTypeECDSA, it invokes newECDSAKeygenSession; for EdDSA, it calls newEDDSAKeygenSession. Each session type implements the same interface but initializes distinct tss-lib curves (ecdsa.EC256 vs edwards.Ed25519) and handles curve-specific serialization in their respective GenerateKey implementations.

Where is the private key material stored after the session completes?

Upon receiving saveData from the tss-lib party, the session serializes the private key and calls kvstore.Put to store it in the configured key-value store (implemented in pkg/kvstore/kvstore.go). Simultaneously, it creates a KeyInfo record in pkg/keyinfo/keyinfo.go containing metadata about participants and thresholds. This dual-storage approach separates sensitive key data from descriptive metadata.

How does the client receive confirmation that key generation succeeded?

Clients subscribe to results using MPCClient.OnWalletCreationResult in pkg/client/client.go, which sets up a subscription to the result stream. When the keygenConsumer finishes processing, it publishes a KeygenResultEvent containing the wallet ID and public key bytes. The client's callback function receives this event, allowing the application to proceed with wallet operations knowing the MPC key generation completed successfully across all participating nodes.

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 →