tinyhumansai/openhuman · error · Error
Ed25519 derivation path must be fully hardened: ${derivation
Error message
Ed25519 derivation path must be fully hardened: ${derivationPath} What it means
SLIP-0010 ed25519 derivation (used for Solana-style keys) only supports hardened segments: ed25519 with non-hardened derivation cannot compute child public keys from public data, so every index must carry the `'` suffix. The loop rejects the first non-hardened segment it encounters.
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 a221052e0d)
Solutions
- Harden every segment: m/44'/501'/0'/0'
- Normalize and validate paths on save: every segment must end with `'`
- Add a test vector for the canonical Solana path
Example fix
// before const path = "m/44'/501'/0'/0"; // after const path = "m/44'/501'/0'/0'";
Defensive patterns
Strategy: validation
Validate before calling
function isHardenedEd25519Path(path: string): boolean {
return /^m(\/\d+')+$/.test(path);
}
// use before calling the ed25519 derivation helper Type guard
function isFullyHardenedPath(p: string): boolean {
return p.split('/').slice(1).every((seg) => seg.endsWith("'"));
} Prevention
- Never reuse secp256k1/BIP-44 path constants for ed25519 chains
- Validate that every segment ends with `'` when the path is configurable
- Keep a canonical Solana vector (m/44'/501'/0'/0') in tests
When it happens
Trigger: Calling the ed25519 helper with a secp256k1-style path such as m/44'/501'/0'/0 — the final segment lacks `'` and trips the check on the first iteration over it.
Common situations: Adding an Ed25519 chain and reusing the EVM path constant; user-configurable paths copied from BIP-44 docs; confusion between BIP-32 (mixed hardening allowed) and SLIP-0010 ed25519 (hardened only).
Related errors
- Failed to derive private key for path ${derivationPath}
- [tunnel] no device keypair
- [tunnel] no session cipher — handshake incomplete
- encrypted payload too short
- invalid AES key: {e}
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/e4f1aeb3cf8b2edf.
Report an issue: GitHub.