tinyhumansai/openhuman · error · Error

Ed25519 derivation path must be fully hardened: ${derivation

Error message

Ed25519 derivation path must be fully hardened: ${derivationPath}

What it means

SLIP-0010 ed25519 derivation (used for Solana-style keys) only supports hardened segments: ed25519 with non-hardened derivation cannot compute child public keys from public data, so every index must carry the `'` suffix. The loop rejects the first non-hardened segment it encounters.

Source

Thrown at app/src/utils/cryptoKeys.ts:152

function deriveSecp256k1PrivateKey(mnemonic: string, derivationPath: string): Uint8Array {
  const seed = mnemonicToSeedSync(mnemonic);
  const hdkey = HDKey.fromMasterSeed(seed);
  const derived = hdkey.derive(derivationPath);
  if (!derived.privateKey) {
    throw new Error(`Failed to derive private key for path ${derivationPath}`);
  }
  return derived.privateKey;
}

function deriveSlip10Ed25519PrivateKey(seed: Uint8Array, derivationPath: string): Uint8Array {
  let key = hmac(sha512, new TextEncoder().encode('ed25519 seed'), seed);
  let privateKey = key.slice(0, 32);
  let chainCode = key.slice(32);

  for (const segment of derivationPath.split('/').slice(1)) {
    if (!segment.endsWith("'")) {
      throw new Error(`Ed25519 derivation path must be fully hardened: ${derivationPath}`);
    }
    const index = Number.parseInt(segment.slice(0, -1), 10);
    const hardened = (index + 0x80000000) >>> 0;
    const data = new Uint8Array(37);
    data[0] = 0;
    data.set(privateKey, 1);
    data[33] = (hardened >>> 24) & 0xff;
    data[34] = (hardened >>> 16) & 0xff;
    data[35] = (hardened >>> 8) & 0xff;
    data[36] = hardened & 0xff;
    key = hmac(sha512, chainCode, data);
    privateKey = key.slice(0, 32);
    chainCode = key.slice(32);
  }

  return privateKey;
}

View on GitHub (pinned to a221052e0d)

Solutions

  1. Harden every segment: m/44'/501'/0'/0'
  2. Normalize and validate paths on save: every segment must end with `'`
  3. Add a test vector for the canonical Solana path

Example fix

// before
const path = "m/44'/501'/0'/0";

// after
const path = "m/44'/501'/0'/0'";
Defensive patterns

Strategy: validation

Validate before calling

function isHardenedEd25519Path(path: string): boolean {
  return /^m(\/\d+')+$/.test(path);
}
// use before calling the ed25519 derivation helper

Type guard

function isFullyHardenedPath(p: string): boolean {
  return p.split('/').slice(1).every((seg) => seg.endsWith("'"));
}

Prevention

When it happens

Trigger: Calling the ed25519 helper with a secp256k1-style path such as m/44'/501'/0'/0 — the final segment lacks `'` and trips the check on the first iteration over it.

Common situations: Adding an Ed25519 chain and reusing the EVM path constant; user-configurable paths copied from BIP-44 docs; confusion between BIP-32 (mixed hardening allowed) and SLIP-0010 ed25519 (hardened only).

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/e4f1aeb3cf8b2edf. Report an issue: GitHub.