3

I'm currently using the Hedera JS SDK to generate a single ECDSA key-pair using a private key directly as an input, like so:

const privateKey = PrivateKey.fromStringECDSA(process.env.TARGET_HEX_PRIVATE_KEY);
    

Instead I would like to do something like this instead, where I am using a BIP-39 seed phrase and a derivation path as inputs:

const mnemonic = Mnemonic.fromString(process.env.TARGET_SEED_PHRASE);
const privateKey = await mnemonic.toEcdsaPrivateKey('', "m/44'/60'/0'/0/0");

However, Mnemonic's toEcdsaPrivateKey function seems to accept an array of numbers as the input for the derivation path, based on its JsDoc @param comment, copied below:

    /**
     * Recover an ECDSA private key from this mnemonic phrase, with an
     * optional passphrase.
     *
     * @param {string} [passphrase]
     * @param {number[]} [path]
     * @returns {Promise<PrivateKey>}
     */
    async toEcdsaPrivateKey(passphrase = "", path) {
        return CACHE.privateKeyConstructor(
            await this._mnemonic.toEcdsaPrivateKey(passphrase, path)
        );
    }

In my use case, I would like to work with MetaMask, which unfortunately does not yet support custom derivation paths per network configuration, and hardcodes the Ethereum derivation path of m/44'/60'/0'/0/0 instead. Note that the first 3 segments are "hardened", while the remaining 2 are not.

How can I specify this derivation path?

bguiz
  • 27,371
  • 47
  • 154
  • 243
  • There are some examples in the documentation, e.g. [here](https://github.com/hashgraph/hedera-sdk-js/blob/1a73f3f1329a48702f2a5170260bd05f186e0ca3/packages/cryptography/src/Mnemonic.js#L35) or [here](https://github.com/hashgraph/hedera-sdk-js/blob/e0cd39c84ab189d59a6bcedcf16e4102d7bb8beb/packages/cryptography/test/unit/Mnemonic.js#L61). – Topaco Apr 13 '23 at 10:15
  • Ah thanks @Topaco - the binary OR with `0x80000000` looks like what I'm after! – bguiz Apr 19 '23 at 00:30

1 Answers1

5

In the latest release, v2.24.1, the Mnemonic class has been updated to deprecate toEcdsaPrivateKey:

    /**
     * @deprecated - Use `toStandardEd25519PrivateKey()` or `toStandardECDSAsecp256k1PrivateKey()` instead
     * Recover an ECDSA private key from this mnemonic phrase, with an
     * optional passphrase.
     * @param {string} [passphrase]
     * @param {number[]} [path]
     * @returns {Promise<PrivateKey>}
     */

... so using the methods suggested by @Topaco in the comments, where each hardened segment is binary OR-ed with 0x80000000, is not going to work. ... and therefore not possible to achieve my intended outcome using only Hedera JS SDK.

So here's a workaround that uses EthersJs in combination with the Hedera JS SDK to achieve the intended outcome:

import { PrivateKey } from "@hashgraph/sdk";
import { utils as ethersUtils } from "ethers";

// init a hierarchically deterministic wallet's node using a seed phrase
const hdNodeRoot = ethersUtils.HDNode.fromMnemonic(process.env.TARGET_SEED_PHRASE);

// apply the derivation path, `accountIdx` starts at `0`
const hdNodeX = hdNodeRoot.derivePath(`m/44'/60'/0'/0/${accountIdx}`);

// convert from ethersjs private key to hedera js sdk private key
const privateKeyX = PrivateKey.fromStringECDSA(hdNodeX.privateKey);

// extract the `0x...` address from the private key
const addressX = privateKeyX.publicKey.toEvmAddress();

The significant bit is where utils.HDNode.fromMnemonic from EthersJs is used in place of Mnemonic.toEcdsaPrivateKey from Hedera JS SDK.


Here's a more complete example, where I create multiple accounts using the above method, and fund them from an ED22519 account (originally generated on and funded by the Hedera Testnet Portal, which sort of functions like a faucet).

bguiz
  • 27,371
  • 47
  • 154
  • 243
  • adding the checkmark to this answer... but if anyone finds a way to do this without this workaround - i.e. using the Hedera JS SDK only - will be happy to switch the checkmark over to the newer answer! – bguiz Apr 28 '23 at 01:43