# How Hierarchical Deterministic (HD) Wallet Derivation Works with CKD in mpcium

> Discover how mpcium leverages BIP-32 HD wallet derivation with CKD. Explore the pure-Go client-side utility and the MPC-aware wrapper for secure child key derivation.

- Repository: [Fystack Labs/mpcium](https://github.com/fystack/mpcium)
- Tags: deep-dive
- Published: 2026-03-02

---

**mpcium implements BIP-32 HD wallet derivation through two complementary Child Key Derivation (CKD) systems—a pure-Go client-side utility in `pkg/ckdutil` for local public key generation, and an MPC-aware wrapper in [`pkg/mpc/ckd.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ckd.go) that securely derives child keys inside threshold signing sessions using tss-lib.**

The mpcium library provides a complete **Hierarchical Deterministic (HD)** wallet implementation that follows the BIP-32 specification for **Child Key Derivation (CKD)**. It supports both standalone client-side derivation for address generation and secure multi-party computation (MPC) integrated derivation, enabling non-hardened child key generation for Ed25519 and Secp256k1 curves while keeping master secrets distributed across threshold nodes.

## The Dual CKD Architecture in mpcium

mpcium provides two distinct CKD implementations to serve different operational contexts:

- **Pure-Go Client-Side CKD** ([`pkg/ckdutil/child_derivation.go`](https://github.com/fystack/mpcium/blob/main/pkg/ckdutil/child_derivation.go)): Implements non-hardened derivation locally for **Ed25519** and **Secp256k1** curves using standard HMAC-SHA-512 operations, allowing clients to generate child public addresses without network interaction.

- **MPC-Aware CKD** ([`pkg/mpc/ckd.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ckd.go)): Wraps the BIP-32 logic from **tss-lib** to perform derivation inside active MPC sessions, enabling distributed key generation and signing while maintaining the secrecy of chain codes and private key shards.

Both implementations follow identical mathematical procedures to ensure derivations remain consistent across client and server contexts.

## Client-Side HD Derivation with ckdutil

The `pkg/ckdutil` package provides standalone functions for **non-hardened** derivation, requiring only the master public key and chain code.

### The BIP-32 Derivation Algorithm

The core logic in `DeriveEd25519ChildCompressed` and `DeriveSecp256k1ChildCompressed` implements the standard CKD algorithm:

1. **Input validation**: Verifies the master key length (32 bytes for Ed25519, 33 bytes for Secp256k1), confirms the chain code is exactly 32 bytes, and ensures every derivation index is non-hardened (`index < 0x80000000`).

2. **Path iteration**: For each index in the derivation path:
   - Serialize the **compressed parent public key** (33 bytes).
   - Append the **4-byte big-endian index** to create HMAC data.
   - Compute `H = HMAC-SHA512(parentChainCode, data)`.
   - Split `H` into `IL` (first 32 bytes) and `IR` (last 32 bytes).
   - Convert `IL` to scalar `k`, rejecting zero or overflow values (≥ curve order).
   - Calculate the **delta point** `Δ = k·G` via scalar multiplication.
   - Perform **point addition**: `P_child = P_parent + Δ`.
   - Set the new **chain code** to `IR`.

3. **Output compression**: Return the final public point as a compressed byte slice.

### Curve-Specific Implementations

For **Ed25519** keys, use `DeriveEd25519ChildCompressed`:

```go
import "github.com/fystack/mpcium/pkg/ckdutil"

childPub, err := ckdutil.DeriveEd25519ChildCompressed(
    masterPubKey,        // []byte - 32-byte Ed25519 public key
    chainCodeHex,        // string - hex-encoded 32-byte chain code
    []uint32{44, 501, 0, 0}, // BIP-44 path: m/44'/501'/0/0
)

```

For **Secp256k1** keys, use `DeriveSecp256k1ChildCompressed`:

```go
childPub, err := ckdutil.DeriveSecp256k1ChildCompressed(
    masterPubKey,        // []byte - 33-byte compressed secp256k1 key
    chainCodeHex,        // string - hex-encoded chain code
    []uint32{44, 0, 0, 0},
)

```

## MPC-Aware CKD for Threshold Signing

When derivation must occur inside the MPC protocol to prevent chain code exposure, [`pkg/mpc/ckd.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ckd.go) provides the `CKD` struct.

### Integration with tss-lib

The MPC-aware implementation wraps `ckd.DeriveChildKeyFromHierarchy` from **tss-lib**, creating a temporary parent extended key that holds:
- The master public key as a `*crypto.ECPoint`
- The derivation depth and parent fingerprint
- The 32-byte chain code (validated via `NewCKDFromHex`)

### Derivation Process Inside MPC Sessions

To derive a child key during an MPC signing session:

```go
import (
    "github.com/fystack/mpcium/pkg/mpc"
    "github.com/bnb-chain/tss-lib/v2/tss"
)

ckd, err := mpc.NewCKDFromHex(chainCodeHex) // validates 32-byte length
if err != nil {
    return err
}

delta, childExtKey, err := ckd.Derive(
    walletID,                // unique wallet identifier
    masterPubECPoint,        // *crypto.ECPoint from tss-lib
    []uint32{44, 501, 0, 0}, // derivation path
    tss.Edwards(),           // or tss.Secp256k1()
)

```

The `Derive` method returns:
- `delta`: The scalar value (`IL`) used for point addition
- `childExtKey`: The extended key containing the derived public point and new chain code

The MPC node updates its internal `BigXj` values using the returned `delta`, synchronizing its state with the derived child key for subsequent signing operations.

## Verifying Derivation Parity Between Client and MPC

The example in [`examples/hdwallet/eddsa/main.go`](https://github.com/fystack/mpcium/blob/main/examples/hdwallet/eddsa/main.go) demonstrates cryptographic verification that both derivation methods produce identical public keys:

```go
// Client-side derivation
clientChild, _ := ckdutil.DeriveEd25519ChildCompressed(
    masterPubKey, chainCodeHex, path,
)

// MPC-side derivation
tssChild, _ := deriveChildPublicKeyEd25519ViaTSS(
    masterPubKey, chainCodeHex, path, walletID,
)

// Parity check
if !slices.Equal(clientChild, tssChild) {
    logger.Warn("Derived child pubkey mismatch")
}

```

This parity check confirms that the local CKD utility and MPC-aware implementation agree on the BIP-32 mathematics, ensuring address generation and signature verification remain consistent.

## Complete End-to-End Implementation

A typical workflow combining master wallet creation and HD derivation follows this pattern:

```go
// 1. Create master wallet via MPC
// Returns master public key via KeygenResultEvent and uses configured chain_code

// 2. Derive child address locally
childPub, _ := ckdutil.DeriveEd25519ChildCompressed(
    masterPubKey, 
    chainCodeHex, 
    []uint32{44, 501, accountIndex, 0},
)

// 3. Generate blockchain address (e.g., Solana)
address := base58.Encode(childPub)

// 4. Sign transaction using derived path
signMsg := &types.SignTxMessage{
    WalletID:       walletID,
    KeyType:        types.KeyTypeEd25519,
    DerivationPath: []uint32{44, 501, accountIndex, 0},
    Tx:             transactionHash,
}
mpcClient.SignTransaction(signMsg)
// The MPC node internally re-derives the same child key via pkg/mpc/ckd.go

```

## Summary

- **mpcium** implements standard **BIP-32 HD wallet derivation** supporting **Ed25519** and **Secp256k1** curves.
- The **ckdutil** package ([`pkg/ckdutil/child_derivation.go`](https://github.com/fystack/mpcium/blob/main/pkg/ckdutil/child_derivation.go)) provides **client-side non-hardened derivation** using HMAC-SHA-512 and elliptic curve point addition.
- The **mpc** package ([`pkg/mpc/ckd.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ckd.go)) wraps **tss-lib** to enable **threshold-friendly CKD** that keeps chain codes secret during distributed signing.
- Both implementations use identical mathematical operations: serializing parent keys, computing HMAC-SHA-512, splitting into `IL`/`IR` scalars, and performing point addition (`P_child = P_parent + k·G`).
- **Parity verification** ensures client-derived public keys match those generated inside MPC sessions, preventing address derivation errors.
- The system exclusively supports **non-hardened derivation** (indices < 0x80000000), requiring the master public key but not the private key for child generation.

## Frequently Asked Questions

### What is the difference between hardened and non-hardened derivation in mpcium?

**mpcium exclusively supports non-hardened derivation** (also called public derivation), where child keys are derived from the parent public key and chain code using indices less than `0x80000000` (2^31). Non-hardened derivation allows child public keys to be computed without access to the parent private key, enabling clients to generate addresses independently. Hardened derivation, which requires the parent private key and uses indices ≥ 2^31, is not implemented in the current codebase to maintain the security properties of threshold signing, where private keys never exist in complete form.

### How does mpcium secure the chain code during MPC operations?

The **chain code** is treated as a 32-byte secret supplied via configuration (`chain_code` key) and validated through `NewCKDFromHex` in [`pkg/mpc/ckd.go`](https://github.com/fystack/mpcium/blob/main/pkg/mpc/ckd.go). During MPC sessions, the chain code remains within the threshold network, never exposed to individual clients or the network layer. The `CKD.Derive` method computes the HMAC-SHA-512 operations inside the secure MPC environment using **tss-lib**, ensuring the chain code participates in child key generation without leaving the distributed signing nodes.

### Which elliptic curves does mpcium support for HD wallet derivation?

mpcium supports **Ed25519** (Edwards-curve Digital Signature Algorithm) and **Secp256k1** (used in Bitcoin and Ethereum) through dedicated functions in [`pkg/ckdutil/child_derivation.go`](https://github.com/fystack/mpcium/blob/main/pkg/ckdutil/child_derivation.go). For Ed25519, use `DeriveEd25519ChildCompressed` with 32-byte public keys. For Secp256k1, use `DeriveSecp256k1ChildCompressed` with 33-byte compressed public keys. The MPC implementation accepts curve specifications via `tss.Edwards()` or `tss.Secp256k1()` when calling `ckd.Derive`.

### How can I verify that client-side and MPC derivations produce matching keys?

The repository provides parity checking examples in [`examples/hdwallet/eddsa/main.go`](https://github.com/fystack/mpcium/blob/main/examples/hdwallet/eddsa/main.go) and [`examples/hdwallet/ecdsa/main.go`](https://github.com/fystack/mpcium/blob/main/examples/hdwallet/ecdsa/main.go). Derive the child public key locally using `ckdutil.DeriveEd25519ChildCompressed` (or the Secp256k1 equivalent), then derive the same path using the MPC-aware function `deriveChildPublicKeyEd25519ViaTSS` which internally calls `mpc.CKD.Derive`. Compare the resulting byte slices using `slices.Equal`; identical 32-byte or 33-byte outputs confirm both implementations follow the same BIP-32 mathematics.