ethers.js: HDNodeWallet derivePath not working properly
Ethers Version
^6.9.2
Search Terms
HDNodeWallet, DerivePath
Describe the Problem
I am trying to derivePath from an HDNodeWallet, but the provided path is not the same path when the wallet is generated for example i am trying to generate a wallet using Mnemonic and a path input path=“m/44’/60’/0’/0/1” output path=“m/44’/60’/0’/0/1/44’/60’/0’/0/1”
Maybe i have a bad understanding of HDWallet but in my mind they should be the same path
Code Snippet
const ethers = require("ethers");
const path = "m/44'/60'/0'/0/1";
const phrase = "word word word word word word word word word word word word";
const mnemonic = ethers.Mnemonic.fromPhrase(phrase);
const wallet = ethers.HDNodeWallet.fromMnemonic(mnemonic, path);
console.log(wallet.path);
// output: m/44'/60'/0'/0/1
const wallet1 = wallet.derivePath(path);
console.log(wallet1.path);
// output: m/44'/60'/0'/0/1/44'/60'/0'/0/1
Contract ABI
No response
Errors
No response
Environment
node.js (v12 or newer)
Environment (Other)
No response
About this issue
- Original URL
- State: closed
- Created 5 months ago
- Comments: 20 (5 by maintainers)
Commits related to this issue
- Throw an error when attempting to derive from a master path from a non-master node (#4551). — committed to ethers-io/ethers.js by ricmoo 5 months ago
So, the above code should have thrown an error. I’m adding that now: the
"m/"
part of the path asserts that the depth of the node is 0, i.e. that you are computing the child from the “master” or root node.I’m adding a constraint that it ensures this is the case, so the code in the OP would throw.
As an aside, for those that wish to compute a large number of child nodes, this should be about 5 times faster, as it keeps a reference to an intermediate node, so those calculations do not need to be replicated:
@niZmosis It already exists, but is called
.deriveChild
, which accepts a number. 😃@martines3000 Working previously was a bug. When you begin a path with
m/
you are indicating you want to enforce the current not is the master node. Previously it was ignored, which is bad. Much of the time the higher nodes are not actually present in memory (a feature baked into the specification), so simply “jumping” to the master node isn’t possible. I could consider adding a feature in the future that would allow crawling back up to it, if enough state is in memory to reconstruct it, but I’d have to think about that more. To get the old address using the buggy version, you can simply leave them/
off the second path to descend into the HD structure the same way. You should move those funds though, as they won’t be accessible using standard tools like ledger or MetaMask.If you need any help with this, let me know. 😃
@Sean329 @martines3000
Here would be your updated code.
` const basePath = “44’/60’/0’/0”; // or “m/44’/60’/0’/0”
const phrase = “word word word word word word word word word word word word” const mnemonic = ethers.Mnemonic.fromPhrase(phrase); const baseWallet = ethers.HDNodeWallet.fromMnemonic(mnemonic, basePath);
const wallet1 = baseWallet.derivePath(‘0’); console.log(wallet1.path);
const wallet2 = baseWallet.derivePath(‘1’); console.log(wallet2.path); `
Notice our basePath leaves off the index, and then when deriving you use what ever index you’d like. Don’t use the baseWallet directly as the path isn’t complete. If you do provide a complete path when making the wallet, it will be valid. But if you go to derive a wallet from it, you will run into the path concatenation problem I showed earlier in the thread.
@ricmoo What do you think about renaming derivePath to deriveIndex and then instead of a string we pass it a number?
EDIT: ricmoo pointed out there is the deriveChild which takes in a number.
` const basePath = “44’/60’/0’/0”; // or “m/44’/60’/0’/0”
const phrase = “word word word word word word word word word word word word” const mnemonic = ethers.Mnemonic.fromPhrase(phrase); const baseWallet = ethers.HDNodeWallet.fromMnemonic(mnemonic, basePath);
const walletViaPath = baseWallet.derivePath(‘0’); console.log(walletViaPath.path);
const walletViaChild = baseWallet.deriveChild(0); console.log(walletViaChild.path); `
Your baseWalletOld isn’t correct. When not providing the path to the fromMnemonic it will use the default path which is “export const defaultPath: string = “m/44’/60’/0’/0/0”;” at which point the base path of the wallet object is now to long to be able to use the derivePath. Because of that default, your “.derivePath(
m/44/1236/1/0/0
)” is now going to concatenate to the defaults. So you must provide the base path like before without the index, then use the derivePath with the index you want, not the full path. Your baseWalletNew is the correct way to use it.@ricmoo Sure, I’m using v6.11.1 and running the code below, and plz look at the log:
I expect the
wallet1.path
to be “m/44’/60’/0’/0/1” instead of being “m/44’/60’/0’/0/0/44’/60’/0’/0/1”. Am I misunderstanding some concepts in here? Thanks.