{"record":{"id":"e4f1aeb3cf8b2edf","repo":"tinyhumansai/openhuman","slug":"ed25519-derivation-path-must-be-fully-hardened","errorCode":null,"errorMessage":"Ed25519 derivation path must be fully hardened: ${derivationPath}","messagePattern":"Ed25519 derivation path must be fully hardened: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/utils/cryptoKeys.ts","lineNumber":152,"sourceCode":"\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);\n    data[33] = (hardened >>> 24) & 0xff;\n    data[34] = (hardened >>> 16) & 0xff;\n    data[35] = (hardened >>> 8) & 0xff;\n    data[36] = hardened & 0xff;\n    key = hmac(sha512, chainCode, data);\n    privateKey = key.slice(0, 32);\n    chainCode = key.slice(32);\n  }\n\n  return privateKey;\n}\n","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/utils/cryptoKeys.ts#L134-L170","documentation":"deriveSlip10Ed25519PrivateKey() implements SLIP-0010 for ed25519, whose spec only permits hardened derivation (ed25519's curve math does not support public-key-only child derivation, so non-hardened children cannot be computed). Every path segment after m must end with an apostrophe; the loop checks each segment and throws on the first unhardened one.","triggerScenarios":"Passing an EVM-style path with unhardened change/index segments (m/44'/60'/0'/0/0) to the ed25519/Solana deriver; reusing a generic path template across chains; the trailing segment missing its ' (m/44'/501'/0'/0).","commonSituations":"Multi-chain wallet UI sharing one derivation-path input; defaults copied from Bitcoin/EVM docs; paths round-tripped through code that strips quotes.","solutions":["Append ' (or use h) to every segment: m/44'/501'/0'/0' is the Solana standard this deriver expects","Validate with /^m(\\/[0-9]+')+$/ before calling so the user gets a field-level error instead of a throw","Keep per-chain path presets and disable free-text editing for ed25519 chains"],"exampleFix":"// before\nconst path = \"m/44'/501'/0'/0\"; // last segment unhardened -> throws\n// after\nconst path = \"m/44'/501'/0'/0'\";","handlingStrategy":"validation","validationCode":"function assertFullyHardened(path: string): void {\n  const segments = path.split('/').slice(1);\n  const ok = segments.length > 0 && segments.every(s => /^[0-9]+'$/.test(s) || /^[0-9]+h$/.test(s));\n  if (!ok) throw new Error(`Ed25519 path must be fully hardened: ${path}`);\n}","typeGuard":"function isHardenedEd25519Path(path: string): boolean {\n  return /^m(?:\\/[0-9]+')+$/.test(path);\n}","tryCatchPattern":null,"preventionTips":["Pin per-chain constants: Solana m/44'/501'/0'/0' (all hardened)","Never reuse EVM change/index segments unhardened for ed25519 chains","Validate at the input field so the user sees the rule before submission"],"tags":["crypto","wallet","ed25519","slip-10","derivation-path"],"backgroundTag":"invalid-derivation-path","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}