tinyhumansai/openhuman · error
Ed25519 derivation path must be fully hardened
Error message
Ed25519 derivation path must be fully hardened: ${derivationPath} What it means
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.
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
Example fix
// before const path = "m/44'/501'/0'/0"; // last segment unhardened -> throws // after const path = "m/44'/501'/0'/0'";
Defensive patterns
Strategy: validation
Validate before calling
function assertFullyHardened(path: string): void {
const segments = path.split('/').slice(1);
const ok = segments.length > 0 && segments.every(s => /^[0-9]+'$/.test(s) || /^[0-9]+h$/.test(s));
if (!ok) throw new Error(`Ed25519 path must be fully hardened: ${path}`);
} Type guard
function isHardenedEd25519Path(path: string): boolean {
return /^m(?:\/[0-9]+')+$/.test(path);
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: Multi-chain wallet UI sharing one derivation-path input; defaults copied from Bitcoin/EVM docs; paths round-tripped through code that strips quotes.
Related errors
- Failed to derive private key for path
- encrypted payload too short
- encryption key must decode to 32 raw bytes
- Failed to secure recovery phrase. Please try again.
- Recovery phrase is required.
AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17).
Data as JSON: /api/errors/e4f1aeb3cf8b2edf.
Report an issue: GitHub.
Appendix: 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 7491200858)