tinyhumansai/openhuman · error · Error
Failed to derive private key for path ${derivationPath}
Error message
Failed to derive private key for path ${derivationPath} What it means
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.
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 a221052e0d)
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
Example fix
// before
const pk = deriveSecp256k1PrivateKey(mnemonic, path);
// after
const BIP32 = /^m(\/\d+'?)+$/;
if (!BIP32.test(path)) {
throw new Error(`invalid BIP-32 derivation path: ${path}`);
}
const pk = deriveSecp256k1PrivateKey(mnemonic, path); Defensive patterns
Strategy: validation
Validate before calling
const BIP32_PATH = /^m(\/\d+'?)+$/;
function isValidBip32Path(path: string): boolean {
return BIP32_PATH.test(path) && path !== 'm';
} Type guard
function isBip32Path(p: string): p is `${'m'}${string}` {
return /^m(\/\d+'?)+$/.test(p);
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Ed25519 derivation path must be fully hardened: ${derivation
- Finish choosing how OpenHuman runs (tap Continue on the setu
- OpenHuman could not reach its remote (cloud) runtime. Check
- agentTeamApi: ${label} must be a positive integer
- agentTeamApi.get: teamId is required
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/127ddc8f9cee6bea.
Report an issue: GitHub.