tinyhumansai/openhuman · error

Failed to derive private key for path

Error message

Failed to derive private key for path ${derivationPath}

What it means

deriveSecp256k1PrivateKey() runs BIP32 (@scure/bip32 HDKey) over mnemonicToSeedSync(mnemonic) and calls hdkey.derive(derivationPath); if the returned node has no privateKey the function refuses to continue. A neutered/public-only node comes back when the path is malformed or the derivation cannot produce a private key for it.

Solutions

  1. Validate the path against ^m(\/[0-9]+'?)+$ before deriving and reject early with a clear message
  2. Use well-known per-chain constants (m/44'/60'/0'/0/i for EVM, m/44'/501'/0'/0' for Solana) instead of free-text input
  3. Ensure apostrophes survive whatever stored/transported the path (h instead of ' also works: m/44h/60h/0h/0h/0)

Example fix

// before
const key = deriveSecp256k1PrivateKey(mnemonic, rawUserInput);
// after
if (!/^m(\/[0-9]+'?)+$/.test(path)) throw new Error(`Invalid derivation path: ${path}`);
const key = deriveSecp256k1PrivateKey(mnemonic, path);
Defensive patterns

Strategy: validation

Validate before calling

const BIP32_PATH = /^m(?:\/[0-9]{1,10}'?)+$/;
function assertBip32Path(path: string): void {
  if (!BIP32_PATH.test(path)) throw new Error(`Invalid BIP-32 path: ${path}`);
}

Type guard

function isBip32Path(path: string): boolean {
  return BIP32_PATH.test(path);
}

Prevention

When it happens

Trigger: A derivation path that is not a valid BIP-32 path: not starting with 'm/', empty segments ('m//0'), unhardened index ≥ 2^31, garbage characters, or a path string the HDKey implementation silently degrades on; also a custom path with unquoted apostrophes mangled by shell/JSON escaping.

Common situations: User-editable derivation-path inputs in wallet settings; copying EVM/Solana paths between chains with wrong formatting; paths assembled by string concat that drop the leading m or the quotes (m/44'/60'/0'/0/0 typed as m/44/60/0/0/0 is fine for secp256k1 but mixed quoting breaks).

Related errors


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

Appendix: 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 7491200858)