tinyhumansai/openhuman · error · Error

Failed to derive private key for path ${derivationPath}

Error message

Failed to derive private key for path ${derivationPath}

What it means

deriveSecp256k1PrivateKey runs BIP-32 HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic)).derive(path) and requires a private key on the resulting node. hdkey can return a node with a null privateKey for degenerate inputs (empty path, bare 'm' with no segments, or a path shape it cannot derive privately), so the guard fires.

Source

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

}

/** Simple checksum: lowercase with 0x, then capitalize by hash. */
function toChecksumAddress(address: string): string {
  const a = address.replace(/^0x/i, '').toLowerCase();
  const hash = bytesToHex(keccak_256(new TextEncoder().encode(a)));
  let result = '0x';
  for (let i = 0; i < 40; i++) {
    result += parseInt(hash[i], 16) >= 8 ? a[i].toUpperCase() : a[i];
  }
  return result;
}

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);

View on GitHub (pinned to a221052e0d)

Solutions

  1. Log and inspect the derivationPath — validate it against /^m(\/\d+'?)+$/ before deriving
  2. Use canonical per-chain constants: m/44'/60'/0'/0/0 (EVM), m/44'/501'/0'/0' (Solana)
  3. Reject user/config-supplied paths early with a validation error naming the path
  4. Unit-test every shipped path constant against a known mnemonic test vector

Example fix

// before
const pk = deriveSecp256k1PrivateKey(mnemonic, path);

// after
const BIP32 = /^m(\/\d+'?)+$/;
if (!BIP32.test(path)) {
  throw new Error(`invalid BIP-32 derivation path: ${path}`);
}
const pk = deriveSecp256k1PrivateKey(mnemonic, path);
Defensive patterns

Strategy: validation

Validate before calling

const BIP32_PATH = /^m(\/\d+'?)+$/;
function isValidBip32Path(path: string): boolean {
  return BIP32_PATH.test(path) && path !== 'm';
}

Type guard

function isBip32Path(p: string): p is `${'m'}${string}` {
  return /^m(\/\d+'?)+$/.test(p);
}

Prevention

When it happens

Trigger: Calling the key-derivation helpers with a malformed BIP-32 path — empty string, 'm' alone, or a non-BIP32 constant copied from elsewhere — while the mnemonic itself is valid.

Common situations: A new chain added with a path constant containing a typo; user/config-supplied derivation paths; a path built by string concatenation that produced '' for one chain.

Related errors


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