How Version Management Works for Wallet Keys Across Reshares in mpcium

Mpcium implements semantic versioning for wallet secret shares, automatically incrementing version numbers during resharing operations to distinguish key layouts while maintaining backward compatibility for legacy nodes.

The mpcium repository provides a threshold MPC (Multi-Party Computation) wallet infrastructure where cryptographic keys undergo periodic resharing to update committee membership. Understanding version management for wallet keys across reshares is essential for developers integrating with the system or operating nodes that must handle both current and legacy wallet states.

The Semantic Versioning Architecture

The system defines two fundamental version constants in pkg/mpc/node.go:

  • DefaultVersion (1): Assigned to all newly generated wallets
  • BackwardCompatibleVersion (0): Used by legacy nodes or pre-resharing operations

This semantic versioning serves three critical purposes: distinguishing old and new key-share layouts, tracking the number of resharing operations a wallet has undergone, and enabling correct key-share selection when nodes rejoin reshared committees.

Initial Key Generation and Storage

When a node creates a new wallet via CreateKeyGenSession, the system establishes the initial version state. In pkg/mpc/node.go (lines 96-100), the function calls either createECDSAKeyGenSession or createEDDSAKeyGenSession, passing DefaultVersion as the initial value.

The system persists two distinct data structures:

  1. KeyInfo records: Stored in the key-info store (pkg/keyinfo/keyinfo.go), containing ParticipantPeerIDs, Threshold, and Version
  2. Raw share data: Marshaled LocalPartySaveData stored in the KV store under the plain walletID key (unversioned) for the initial generation

This unversioned storage for the first generation ensures backward compatibility while establishing the baseline version state.

The Resharing Flow: Version Increment

When a resharing operation begins, Node.CreateReshareSession (in pkg/mpc/node.go, lines 60-68) loads the current KeyInfo.Version and passes it to the session constructor. The critical version increment occurs inside the resharing execution.

In pkg/mpc/ecdsa_resharing_session.go (lines 85-90), the Reshare method implements the version bump:

newVersion := s.GetVersion() + 1
key := s.composeKey(walletIDWithVersion(s.walletID, newVersion))

The new share data is then marshaled and stored under this versioned key following the pattern walletID_vN. Simultaneously, the code updates the KeyInfo record (lines 92-99) to reflect the new version and revised participant list.

An identical flow exists in pkg/mpc/eddsa_resharing_session.go for EdDSA keys.

Party ID Generation and Collision Prevention

To prevent cryptographic collisions during resharing, party IDs embed the wallet version in their key field. The createPartyID logic in pkg/mpc/party_id.go (lines 45-50) implements this distinction:

  • For BackwardCompatibleVersion (0): The party ID key is simply the node ID
  • For all other versions: The party ID key follows the format <nodeID>:<version>

This ensures that different generations of the same wallet produce unique party identifiers, preventing interference between old and new committees during the resharing transition.

Retrieving Shares with Version Awareness

When loading wallet data for signing or further resharing, the system uses loadOldShareDataGeneric in pkg/mpc/session.go. This function implements a hierarchical lookup strategy:

  1. If the requested version > 0, it composes the key using walletIDWithVersion (e.g., walletID_v2)
  2. If the versioned key is missing or version == 0, it gracefully falls back to the unversioned walletID key

This logic appears in pkg/mpc/session.go (lines 49-57 and 77-84), ensuring that nodes can always locate their share data regardless of when they last participated in the wallet's evolution.

The Node.getVersion method (lines 54-69 in pkg/mpc/node.go) provides the entry point for determining a wallet's current version, returning DefaultVersion when no KeyInfo record exists.

Practical Implementation Examples

Retrieving the Current Wallet Version

// p is a *Node
walletID := "my-wallet"
ver := p.getVersion(mpc.SessionTypeECDSA, walletID)
// ver will be DefaultVersion (1) for a freshly generated wallet

Loading Shares Across Versions

// s is a *session (created by key-gen, sign, or resharing)
var share keygen.LocalPartySaveData
// Load using the session's known version
if err := s.loadOldShareDataGeneric(walletID, s.version, &share); err != nil {
    return fmt.Errorf("cannot load share: %w", err)
}

Executing a Version Bump During Resharing

// Inside ecdsaReshareSession.Reshare
newVersion := s.GetVersion() + 1
kvKey := s.composeKey(walletIDWithVersion(s.walletID, newVersion))

bytes, _ := json.Marshal(newShareData)
if err := s.kvstore.Put(kvKey, bytes); err != nil {
    return fmt.Errorf("failed to persist reshared share: %w", err)
}

// Update KeyInfo with new participants and version
info := keyinfo.KeyInfo{
    ParticipantPeerIDs: s.newPeerIDs,
    Threshold:          s.reshareParams.NewThreshold(),
    Version:            newVersion,
}
if err := s.keyinfoStore.Save(s.composeKey(s.walletID), &info); err != nil {
    return fmt.Errorf("failed to update keyinfo: %w", err)
}

Generating Version-Aware Party IDs

// node is *Node, readyPeers is []string
selfID, allIDs := node.generatePartyIDs(
    mpc.PurposeKeygen,
    readyPeers,
    node.getVersion(mpc.SessionTypeECDSA, walletID),
)

Summary

  • Semantic versioning distinguishes wallet generations through DefaultVersion (1) and BackwardCompatibleVersion (0) constants defined in pkg/mpc/node.go
  • Version incrementation occurs atomically during resharing in ecdsa_resharing_session.go and eddsa_resharing_session.go, storing new shares under walletID_vN keys
  • KeyInfo records track the current version alongside participant lists and thresholds in the dedicated key-info store
  • Collision prevention is achieved by embedding version numbers into party ID keys in pkg/mpc/party_id.go
  • Backward compatibility is maintained through graceful fallback to unversioned keys when version == 0 in pkg/mpc/session.go

Frequently Asked Questions

What happens if a node tries to load a wallet version it doesn't recognize?

The node calls loadOldShareDataGeneric which implements a fallback mechanism. If the versioned key is unavailable, the system attempts to load the unversioned key, allowing legacy nodes to operate on pre-reshared wallets while newer nodes access versioned data.

How does the system prevent version collisions during active resharing?

Party IDs embed the version number in their key field (as <nodeID>:<version> for non-zero versions) through the createPartyID function in pkg/mpc/party_id.go. This ensures that old and new committees generate distinct cryptographic identities even when processing the same wallet ID.

Where is the version number physically stored?

The version is stored in two locations: the KeyInfo struct in the key-info store (alongside participants and threshold) and implicitly in the KV store key name (as walletID_vN suffixes) for the actual share data. The KeyInfo record serves as the authoritative source of truth.

Can the version number decrease during a resharing operation?

No. The resharing logic explicitly increments the version using newVersion := s.GetVersion() + 1 in both ECDSA and EdDSA resharing sessions. This monotonic increase ensures that wallet evolution is strictly forward-moving and audit trails remain intact.

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 →