# Derive Keys from a Seed

## Overview

Use a cryptographically strong root seed to deterministically derive keys for multiple
algorithms. [`Secp256k1`](/api/Secp256k1), [`P256`](/api/P256),
[`Ed25519`](/api/Ed25519), [`MlDsa44`](/api/MlDsa44), and [`AesGcm`](/api/AesGcm) each expose
`fromSeed` and `fromMnemonic` functions.

Each `fromSeed` function applies its own fixed, versioned HMAC-SHA256 derivation domain. The same
seed therefore reproduces the same key for a given module without reusing raw key material
across algorithms.

```ts twoslash
import { AesGcm, Ed25519, Hex, MlDsa44, P256, Secp256k1 } from 'ox'

const seed = Hex.random(32)

const secp256k1PrivateKey = Secp256k1.fromSeed(seed)
const p256PrivateKey = P256.fromSeed(seed)
const ed25519PrivateKey = Ed25519.fromSeed(seed)
const mlDsa44PrivateKey = MlDsa44.fromSeed(seed)
const encryptionKey = await AesGcm.fromSeed(seed)
```

:::warning
Do not pass a password or other human-readable text directly to `fromSeed`. Passwords do not
contain enough entropy, and `fromSeed` does not perform password stretching. Use a password KDF
first, or generate at least 32 random bytes.
:::

## Recipes

### Generate a Root Seed

[`Bytes.random`](/api/Bytes/random) uses the platform's cryptographically secure random number
generator. Store the root seed as the single secret needed to reproduce every derived key.

```ts twoslash
import { AesGcm, Bytes, Ed25519, MlDsa44, P256, Secp256k1 } from 'ox'

const seed = Bytes.random(32) // [!code hl]

const secp256k1PrivateKey = Secp256k1.fromSeed(seed)
const p256PrivateKey = P256.fromSeed(seed)
const ed25519PrivateKey = Ed25519.fromSeed(seed)
const mlDsa44PrivateKey = MlDsa44.fromSeed(seed)
const encryptionKey = await AesGcm.fromSeed(seed)
```

`AesGcm.fromSeed` is async and returns a non-extractable AES-256-GCM `CryptoKey`. The signing
modules return a hex private key by default; pass `{ as: 'Bytes' }` when mutable bytes are more
appropriate.

### Derive a Seed from a Password

Use [`Keystore.scryptAsync`](/api/Keystore/scryptAsync) when the root secret is a strong password
instead of random bytes. Scrypt derives a 32-byte seed and makes each password guess more
expensive, but it does not increase the password's entropy.

```ts twoslash
import { AesGcm, Bytes, Keystore, P256, Secp256k1 } from 'ox'

const salt = Bytes.random(32)
const [getSeed] = await Keystore.scryptAsync({
  password: 'your-long-unique-password',
  salt,
})
const seed = getSeed()

const secp256k1PrivateKey = Secp256k1.fromSeed(seed)
const p256PrivateKey = P256.fromSeed(seed)
const encryptionKey = await AesGcm.fromSeed(seed)
```

Store the salt and scrypt parameters with your account metadata or backup. They are not secret,
but the same values are required to reproduce the seed. `getSeed` returns the already-derived
key; calling it does not run scrypt again.

:::warning
Scrypt does not prevent brute-force attacks. Never use short or common text such as `lolcats`.
Prefer a long, unique password, a recovery phrase, or a randomly generated root seed.
:::

### Restore Keys from a Recovery Phrase

Pass a BIP-39 recovery phrase directly to each module's `fromMnemonic` function. An optional
`passphrase` is forwarded to the BIP-39 seed derivation.

```ts twoslash
import { AesGcm, Ed25519, MlDsa44, Mnemonic, P256, Secp256k1 } from 'ox'

const phrase = Mnemonic.random(Mnemonic.english)

const secp256k1PrivateKey = Secp256k1.fromMnemonic(phrase)
const p256PrivateKey = P256.fromMnemonic(phrase)
const ed25519PrivateKey = Ed25519.fromMnemonic(phrase)
const mlDsa44PrivateKey = MlDsa44.fromMnemonic(phrase)
const encryptionKey = await AesGcm.fromMnemonic(phrase)
```

`Secp256k1.fromMnemonic` is equivalent to `Mnemonic.toPrivateKey` and uses the standard
`m/44'/60'/0'/0/0` path by default. Pass a `path` option to derive another account.

The other `fromMnemonic` functions are equivalent to
`fromSeed(Mnemonic.toSeed(phrase, { passphrase }))`.

:::warning
P256, Ed25519, ML-DSA-44, and AES-GCM mnemonic-derived keys use Ox's `fromSeed` derivation domains.
They are not BIP-32 child keys. Only `Secp256k1.fromMnemonic` follows a BIP-32 derivation path.
:::

## Best Practices

### Protect the Root Seed

Anyone with the root seed can reproduce every derived key. Keep it out of logs, analytics, URLs,
and plaintext storage. Back it up with the same protections as a wallet recovery phrase.

### Keep the Derivation Contract Stable

Each module's versioned domain is part of its deterministic derivation contract. Re-derive keys
with the same Ox API and seed instead of implementing the HMAC construction separately.

### Use HD Paths for Multiple Ethereum Accounts

`Secp256k1.fromMnemonic` derives an interoperable Ethereum account at its default path. Pass a
different `path`, or use [`HdKey`](/api/HdKey) or [`Mnemonic`](/api/Mnemonic), when you need
multiple accounts.

## See More

<Cards>
  <Card icon="lucide:list-tree" title="Mnemonics & HD Wallets" description="Derive interoperable Ethereum accounts with BIP-32 paths." to="/guides/accounts/mnemonics-hd" />

  <Card icon="lucide:fingerprint" title="Derive Secrets with PRF" description="Derive keys from a WebAuthn passkey instead of a stored seed." to="/guides/webauthn/prf" />

  <Card icon="lucide:lock" title="Work with AES-GCM" description="Encrypt and decrypt data with the derived encryption key." to="/guides/crypto/encryption" />
</Cards>
