> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anchorage.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Generating child control keys

> Derive the per-wallet child control key that authorizes self-custody API transfers.

Self-custody organizations hold their wallet keys under a Master Control Key (MCK) that only the client possesses. The wallet private keys stored in Anchorage Digital's hardware security modules are encrypted to your MCK, so Anchorage Digital cannot sign a transaction until you supply the matching per-wallet child control key. To authorize a transfer through the API, derive that child control key from your MCK and send it in the `Api-Child-Control-Key` header of `POST /v2/transfers`.

<Warning>
  Never send the Master Control Key itself to Anchorage Digital. Only the derived
  per-wallet child control key is transmitted. Perform the derivation on
  infrastructure you control and treat both keys as secrets.
</Warning>

## Derivation inputs

| Value            | Notes                                                                                                                                                                                                                                                                                |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `mck`            | A 32-byte secret, representable as a 24-word BIP39 mnemonic or as a 64-character hex string. Both encodings decode to the same bytes.                                                                                                                                                |
| `derivationPath` | Per-wallet value returned by `GET /v2/wallets/{walletId}`. The value never changes for a given wallet. Always fetch it from the API and pass it unmodified to the derivation code below, which walks its steps; never construct a path yourself or attach meaning to its components. |

The derivation is deterministic: the same MCK and derivation path always produce the same child control key, so you can derive on demand and avoid storing child keys.

## How derivation works

The scheme is standard BIP39 and BIP32, so mature libraries cover all of the cryptography:

1. Decode the MCK. If you hold the 24-word mnemonic, decode it to the 32-byte entropy (`mckFromMnemonic` below). If you hold the MCK as a hex string, hex-decode it to the 32 bytes.
2. Re-encode the entropy as a BIP39 mnemonic and stretch it into the 64-byte BIP39 seed with an empty passphrase.
3. Create a BIP32 master key from the seed and insert Anchorage Digital's hardened purpose step `1097753448'` as the first step after `m`. The wallet's `derivationPath` is relative to this purpose level.
4. Derive the remaining path steps and take the resulting node's 32-byte private key.
5. Hex-encode those 32 bytes (64 lowercase hex characters) for the `Api-Child-Control-Key` header.

## Derive a child control key

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    /**
     * Anchorage Digital child control key derivation reference implementation.
     *
     * Required dependencies:
     *   @scure/bip32@^1.4
     *   @scure/bip39@^1.3
     */

    import { HDKey } from "@scure/bip32";
    import { entropyToMnemonic, mnemonicToEntropy, mnemonicToSeedSync } from "@scure/bip39";
    import { wordlist } from "@scure/bip39/wordlists/english";

    // First derivation step after "m" for every Anchorage control key. The
    // wallet derivationPath returned by GET /v2/wallets/{walletId} is
    // relative to this hardened purpose level.
    const ANCHORAGE_PURPOSE = "1097753448'";

    /**
     * Decode the Recovery Document's 24-word mnemonic into the 32-byte MCK.
     * The BIP39 checksum is validated.
     */
    export function mckFromMnemonic(mnemonicWords: string): Uint8Array {
      return mnemonicToEntropy(mnemonicWords, wordlist);
    }

    /**
     * Derive a wallet's 32-byte child control key from the MCK.
     * `derivationPath` is the value returned by GET /v2/wallets/{walletId}.
     */
    export function deriveChildControlKey(mck: Uint8Array, derivationPath: string): Uint8Array {
      const mnemonic = entropyToMnemonic(mck, wordlist);
      const seed = mnemonicToSeedSync(mnemonic);
      const path = ["m", ANCHORAGE_PURPOSE, ...pathSteps(derivationPath)].join("/");
      const node = HDKey.fromMasterSeed(seed).derive(path);
      if (!node.privateKey) {
        throw new Error(`no private key at ${derivationPath}`);
      }
      return node.privateKey;
    }

    function pathSteps(derivationPath: string): string[] {
      const steps = derivationPath.split("/").filter((step) => step !== "");
      if (steps[0] !== "m") {
        throw new Error(`derivation path must start with 'm': ${derivationPath}`);
      }
      // BIP32 hardened steps may be written with a trailing h; HDKey expects '.
      return steps.slice(1).map((step) => (step.endsWith("h") ? `${step.slice(0, -1)}'` : step));
    }

    export function toHex(bytes: Uint8Array): string {
      return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
    }

    // Example
    const mnemonic =
      "wish length people install bundle crop jacket wolf stove calm blind ramp " +
      "solution agent bean exhibit buddy knee club correct hurt riot rice either";
    const mck = mckFromMnemonic(mnemonic);
    const childKey = deriveChildControlKey(mck, "m/0/0/0");
    console.log(`Api-Child-Control-Key: ${toHex(childKey)}`);
    // Api-Child-Control-Key: 85a087d378cc3e215361aa764e48b6fbafd6a44805bcc371d7b4ddc80ea46f81
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    """Anchorage Digital child control key derivation reference implementation.

    Required dependency:
        bip-utils>=2.9
    """

    from bip_utils import (
        Bip32Slip10Secp256k1,
        Bip39MnemonicDecoder,
        Bip39MnemonicGenerator,
        Bip39SeedGenerator,
    )

    # First derivation step after "m" for every Anchorage control key. The
    # wallet derivationPath returned by GET /v2/wallets/{walletId} is
    # relative to this hardened purpose level.
    ANCHORAGE_PURPOSE = "1097753448'"


    def mck_from_mnemonic(mnemonic_words: str) -> bytes:
        """Decode the Recovery Document's 24-word mnemonic into the 32-byte MCK.

        The BIP39 checksum is validated.
        """
        return bytes(Bip39MnemonicDecoder().Decode(mnemonic_words))


    def derive_child_control_key(mck: bytes, derivation_path: str) -> bytes:
        """Derive a wallet's 32-byte child control key from the MCK.

        derivation_path is the value returned by GET /v2/wallets/{walletId}.
        """
        mnemonic = Bip39MnemonicGenerator().FromEntropy(mck)
        seed = Bip39SeedGenerator(mnemonic).Generate()
        node = Bip32Slip10Secp256k1.FromSeed(seed)
        for step in _path_steps(derivation_path):
            node = node.ChildKey(_step_index(step))
        return node.PrivateKey().Raw().ToBytes()


    def _path_steps(derivation_path: str) -> list[str]:
        steps = [s for s in derivation_path.split("/") if s]
        if not steps or steps[0] != "m":
            raise ValueError(f"derivation path must start with 'm': {derivation_path!r}")
        return [ANCHORAGE_PURPOSE, *steps[1:]]


    def _step_index(step: str) -> int:
        # BIP32 hardened steps may be written with a trailing ' or h.
        hardened = step.endswith(("'", "h"))
        index = int(step[:-1] if hardened else step)
        return index + 0x80000000 if hardened else index


    if __name__ == "__main__":
        mnemonic = (
            "wish length people install bundle crop jacket wolf stove calm blind ramp "
            "solution agent bean exhibit buddy knee club correct hurt riot rice either"
        )
        mck = mck_from_mnemonic(mnemonic)
        child_key = derive_child_control_key(mck, "m/0/0/0")
        print(f"Api-Child-Control-Key: {child_key.hex()}")
        # Api-Child-Control-Key: 85a087d378cc3e215361aa764e48b6fbafd6a44805bcc371d7b4ddc80ea46f81
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Anchorage Digital child control key derivation reference implementation.
    //
    // Required dependencies:
    //
    //	github.com/btcsuite/btcd/btcutil v1.1.6
    //	github.com/tyler-smith/go-bip39 v1.1.0
    package derivecontrolkey

    import (
    	"fmt"
    	"strconv"
    	"strings"

    	"github.com/btcsuite/btcd/btcutil/hdkeychain"
    	"github.com/btcsuite/btcd/chaincfg"
    	bip39 "github.com/tyler-smith/go-bip39"
    )

    // anchoragePurpose is the first derivation step after "m" for every
    // Anchorage control key, as a hardened index. The wallet derivationPath
    // returned by GET /v2/wallets/{walletId} is relative to this purpose level.
    const anchoragePurpose uint32 = 1097753448 + hdkeychain.HardenedKeyStart

    // MCKFromMnemonic decodes the Recovery Document's 24-word mnemonic into
    // the 32-byte MCK. The BIP39 checksum is validated.
    func MCKFromMnemonic(mnemonicWords string) ([]byte, error) {
    	return bip39.EntropyFromMnemonic(mnemonicWords)
    }

    // DeriveChildControlKey derives a wallet's 32-byte child control key from
    // the MCK. derivationPath is the value returned by
    // GET /v2/wallets/{walletId}.
    func DeriveChildControlKey(mck []byte, derivationPath string) ([]byte, error) {
    	mnemonic, err := bip39.NewMnemonic(mck)
    	if err != nil {
    		return nil, err
    	}
    	seed := bip39.NewSeed(mnemonic, "")

    	node, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
    	if err != nil {
    		return nil, err
    	}
    	steps, err := pathSteps(derivationPath)
    	if err != nil {
    		return nil, err
    	}
    	for _, step := range steps {
    		if node, err = node.Derive(step); err != nil {
    			return nil, err
    		}
    	}
    	privateKey, err := node.ECPrivKey()
    	if err != nil {
    		return nil, err
    	}
    	return privateKey.Serialize(), nil
    }

    func pathSteps(derivationPath string) ([]uint32, error) {
    	var parts []string
    	for _, part := range strings.Split(derivationPath, "/") {
    		if part != "" {
    			parts = append(parts, part)
    		}
    	}
    	if len(parts) == 0 || parts[0] != "m" {
    		return nil, fmt.Errorf("derivation path must start with 'm': %q", derivationPath)
    	}

    	steps := []uint32{anchoragePurpose}
    	for _, part := range parts[1:] {
    		// BIP32 hardened steps may be written with a trailing ' or h.
    		hardened := strings.HasSuffix(part, "'") || strings.HasSuffix(part, "h")
    		if hardened {
    			part = part[:len(part)-1]
    		}
    		index, err := strconv.ParseUint(part, 10, 32)
    		if err != nil || index >= hdkeychain.HardenedKeyStart {
    			return nil, fmt.Errorf("invalid derivation step %q in %q", part, derivationPath)
    		}
    		if hardened {
    			index += hdkeychain.HardenedKeyStart
    		}
    		steps = append(steps, uint32(index))
    	}
    	return steps, nil
    }

    // Example:
    //
    //	mck, _ := MCKFromMnemonic("wish length people install bundle crop jacket wolf stove calm blind ramp solution agent bean exhibit buddy knee club correct hurt riot rice either")
    //	childKey, _ := DeriveChildControlKey(mck, "m/0/0/0")
    //	fmt.Printf("Api-Child-Control-Key: %x\n", childKey)
    //	// Api-Child-Control-Key: 85a087d378cc3e215361aa764e48b6fbafd6a44805bcc371d7b4ddc80ea46f81
    ```
  </Tab>
</Tabs>

<Note>
  The mnemonic in the examples is a published test key, not a real
  organization's MCK. Run your implementation against it and confirm you get
  the same child control key before using a production Recovery Document.
</Note>

## Use the key in a transfer

1. Call `GET /v2/wallets/{walletId}` for the source wallet and read `derivationPath` from the response.
2. Derive the child control key and hex-encode it (64 lowercase hex characters).
3. Include it when creating the transfer:

```bash theme={null}
curl "https://api.anchorage-staging.com/v2/transfers" \
  --json @transfer.json \
  -H "Api-Access-Key: $API_ACCESS_KEY" \
  -H "Api-Signature: $API_SIGNATURE" \
  -H "Api-Timestamp: $API_TIMESTAMP" \
  -H "Api-Child-Control-Key: $CHILD_CONTROL_KEY"
```

The `Api-Child-Control-Key` header is required only for self-custody organizations initiating transfers through the API. The `Api-Signature` and `Api-Timestamp` headers carry the standard Ed25519 request signature that `POST /v2/transfers` always requires; see [Signing requests](/knowledge-base/porto/developers/request-signing) for how to compute them.
