{"record":{"id":"127ddc8f9cee6bea","repo":"tinyhumansai/openhuman","slug":"failed-to-derive-private-key-for-path-derivation","errorCode":null,"errorMessage":"Failed to derive private key for path ${derivationPath}","messagePattern":"Failed to derive private key for path (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/src/utils/cryptoKeys.ts","lineNumber":140,"sourceCode":"}\n\n/** Simple checksum: lowercase with 0x, then capitalize by hash. */\nfunction toChecksumAddress(address: string): string {\n  const a = address.replace(/^0x/i, '').toLowerCase();\n  const hash = bytesToHex(keccak_256(new TextEncoder().encode(a)));\n  let result = '0x';\n  for (let i = 0; i < 40; i++) {\n    result += parseInt(hash[i], 16) >= 8 ? a[i].toUpperCase() : a[i];\n  }\n  return result;\n}\n\nfunction deriveSecp256k1PrivateKey(mnemonic: string, derivationPath: string): Uint8Array {\n  const seed = mnemonicToSeedSync(mnemonic);\n  const hdkey = HDKey.fromMasterSeed(seed);\n  const derived = hdkey.derive(derivationPath);\n  if (!derived.privateKey) {\n    throw new Error(`Failed to derive private key for path ${derivationPath}`);\n  }\n  return derived.privateKey;\n}\n\nfunction deriveSlip10Ed25519PrivateKey(seed: Uint8Array, derivationPath: string): Uint8Array {\n  let key = hmac(sha512, new TextEncoder().encode('ed25519 seed'), seed);\n  let privateKey = key.slice(0, 32);\n  let chainCode = key.slice(32);\n\n  for (const segment of derivationPath.split('/').slice(1)) {\n    if (!segment.endsWith(\"'\")) {\n      throw new Error(`Ed25519 derivation path must be fully hardened: ${derivationPath}`);\n    }\n    const index = Number.parseInt(segment.slice(0, -1), 10);\n    const hardened = (index + 0x80000000) >>> 0;\n    const data = new Uint8Array(37);\n    data[0] = 0;\n    data.set(privateKey, 1);","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/utils/cryptoKeys.ts#L122-L158","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log and inspect the derivationPath — validate it against /^m(\\/\\d+'?)+$/ before deriving","Use canonical per-chain constants: m/44'/60'/0'/0/0 (EVM), m/44'/501'/0'/0' (Solana)","Reject user/config-supplied paths early with a validation error naming the path","Unit-test every shipped path constant against a known mnemonic test vector"],"exampleFix":"// before\nconst pk = deriveSecp256k1PrivateKey(mnemonic, path);\n\n// after\nconst BIP32 = /^m(\\/\\d+'?)+$/;\nif (!BIP32.test(path)) {\n  throw new Error(`invalid BIP-32 derivation path: ${path}`);\n}\nconst pk = deriveSecp256k1PrivateKey(mnemonic, path);","handlingStrategy":"validation","validationCode":"const BIP32_PATH = /^m(\\/\\d+'?)+$/;\nfunction isValidBip32Path(path: string): boolean {\n  return BIP32_PATH.test(path) && path !== 'm';\n}","typeGuard":"function isBip32Path(p: string): p is `${'m'}${string}` {\n  return /^m(\\/\\d+'?)+$/.test(p);\n}","tryCatchPattern":null,"preventionTips":["Centralize derivation paths as constants covered by mnemonic test vectors","Validate paths at config-parse time, not at signing time","Reject empty and bare-'m' paths explicitly"],"tags":["crypto","wallet","bip32","derivation","validation"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}