-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HDNodeWallet derivePath not working properly #4551
Comments
Appears the deviation paths have been different since 6.0.0. We may have to revert to 5.7.2 if you want to derive accounts with the BIP44. As you can see from these tests, the base account from wallet is correct, but if you derive an account they are not correct, and the base account doesn't match derived account 0. Test with 5.7.2: function test() { const wallet = ethers.Wallet.fromMnemonic(seedPhrase) const path1 = const metaMaskAccount1 = '0x904c2b17cdf69198eed7004E6b4C99e4C1DdB930' console.log('Base Wallet Address:', wallet.address) test()` Results: Test with >=6.0.0 const wallet = Wallet.fromPhrase(seedPhrase) const metaMaskAccount1 = '0x904c2b17cdf69198eed7004E6b4C99e4C1DdB930' console.log('Base Wallet Address:', wallet.address) Results: |
Alright I found out what is causing it, and found a temp work around as well. So when you call derivePath, it will call that wallet instances "deriveChild" function. Once a wallet is instantiated, it will hold on to its deviation path with the class prop called "path". So when you call derivePath(), the class will go call deriveChild for each part of the path. What happens is it appends that class prop "path" when making the deviated path which isn't right and which is why it ends up like "m/44'/60'/0'/0/0/44'/60'/0'/0/1". This is why it works fine when first making the wallet as "path" is an empty string. It goes deeper and not sure the full intention of the code so I will leave it at that, as this is more than adding a line of code to fix. But for a work around, do not use "deviatePath()", use the static functions. Here is where the problem is in hdwallet.ts ` /**
}` Workaround: `const { ethers } = require("ethers") function test() { const wallet = ethers.HDNodeWallet.fromPhrase(seedPhrase) const path1 = const metaMaskAccount1 = '0x904c2b17cdf69198eed7004E6b4C99e4C1DdB930' console.log('Wallet Address:', wallet.address) test()` Results:
|
Hello thank you mate very good explanation |
We found a work around but the issue you brought up is still a problem if you can reopen it. |
I want to keep this open to further investigate. I’ll close this again if it is deemed not an issue. There are also two functions |
So, the above code should have thrown an error. I'm adding that now: the 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: const wallet = ethers.HDNodeWallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0");
const wallet1 = wallet.derivePath("0");
console.log(wallet1);
const wallet2 = wallet.derivePath("1");
console.log(wallet2);
// Or in a for loop:
for (let i = 0; i < 10; i++) {
console.log(wallet.deriveChild(i));
} |
These changes were published in v6.11.1. Thanks! :) |
I think the initial issue for which this ticket was opened is still there with v6.11.1 : input path="m/44'/60'/0'/0/1" So, is this intentional and not an issue? Otherwise why this ticket is closed? |
@Sean329 No that is not the intended behaviour. This problem should be corrected in the above change, which is to throw if an attempt is made to declare the current node is the root (i.e. master) node when it isn’t. Can you provide sample code that demonstrates the issue you are having? |
@ricmoo Sure, I'm using v6.11.1 and running the code below, and plz look at the log:
I expect the |
I am also getting this error: The code worked fine before the update to the latest version. I am kinda confused now. Should my code not work or has the new update introduced a bug ? |
Here would be your updated code. ` const phrase = "word word word word word word word word word word word word" const wallet1 = baseWallet.derivePath('0'); const wallet2 = baseWallet.derivePath('1'); 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 phrase = "word word word word word word word word word word word word" const walletViaPath = baseWallet.derivePath('0'); const walletViaChild = baseWallet.deriveChild(0); |
Thanks for the fast reply. Yes this indeed solved my issue. I also double checked that I get the same addresses (as with the old approach) with a simple script and it looks good. 💯 EDIT: I didn't test it correctly. If I try it with the following code, I get a different address. const baseWalletOld =
HDNodeWallet.fromMnemonic(mnemonic).derivePath(`m/44/1236/1/0/0`);
const baseWalletNew = HDNodeWallet.fromMnemonic(
mnemonic,
`m/44/1236/1/0`
).derivePath('0');
console.log(`Old: ${baseWalletOld.address}`);
console.log(`New: ${baseWalletNew.address}`); |
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( |
@niZmosis It already exists, but is called @martines3000 Working previously was a bug. When you begin a path with If you need any help with this, let me know. :) |
@ricmoo |
What ever you use for the base path when making the wallet instance, will be used for the derived paths. If you don't specify a path when creating the wallet, it will prefix with |
@niZmosis But my path got 1 extra |
No, it sounds like the base path of the wallet was set with the full path, so when you derive it is appending to that ("44'/60'/0'/0/0") instead of ("44'/60'/0'/0"). Pretty much the wallet object you make, you won't be using directly if you are planning on deriving wallets from it. ` const baseWallet = ethers.HDNodeWallet.fromMnemonic( console.log( |
like a charm :D , ty to comment of @niZmosis , for V "ethers": "^6.13.1", import { HDNodeWallet, Mnemonic, ethers } from 'ethers';
class MyProvider {
wCreate() {
const wallet = ethers.Wallet.createRandom();
const mnemonic = wallet?.mnemonic?.phrase;
if(!mnemonic) throw new Error('Error creating wallet');
return {
mnemonic,
privateKey: wallet.privateKey,
address: wallet.address
};
}
wCreateSubwallet(mnemonic: string, derivationPath: string = "44'/60'/0'/0/0") {
// index nodex obtained from mnemonic
const mnenomicInstance = Mnemonic.fromPhrase(mnemonic);
const masterNode = HDNodeWallet.fromMnemonic(mnenomicInstance);
// Derivation path for subwallet
const subwalletNode = masterNode.derivePath(derivationPath);
return {
mnemonic: mnemonic,
privateKey: subwalletNode.privateKey,
address: subwalletNode.address
};
}
} THE TEST for JEST import { describe, expect, test } from '@jest/globals';
import { MyProvider } from '../src/MyProvider';
describe("Can use 'index' namespace", () => {
let walletUser = {
mnemonic: '',
privateKey: '',
address: '',
};
async function walletsFixture() {
const MyProvider = new MyProvider();
return { MyProvider, walletUser };
}
test('instance class: MyProvider', () => {
expect(new MyProvider() instanceof MyProvider).toBe(true);
});
test('[method] wCreateSubwallet: Generate subwallet from mnemonic', async () => {
const { cypherProvider } = await walletsFixture();
const newWallet = cypherProvider.wCreate();
console.log(newWallet);
const subWallet = cypherProvider.wCreateSubwallet(newWallet.mnemonic, 0);
console.log(subWallet);
expect(subWallet).toHaveProperty('privateKey');
expect(subWallet).toHaveProperty('address');
expect(subWallet).toHaveProperty('mnemonic');
expect(subWallet.mnemonic).toBe(newWallet.mnemonic);
const subWalletTwo = cypherProvider.wCreateSubwallet(newWallet.mnemonic, 1);
console.log(subWalletTwo);
expect(subWalletTwo).toHaveProperty('privateKey');
expect(subWalletTwo).toHaveProperty('address');
expect(subWalletTwo).toHaveProperty('mnemonic');
expect(subWalletTwo.mnemonic).toBe(newWallet.mnemonic);
});
}); tests logs
|
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
Contract ABI
No response
Errors
No response
Environment
node.js (v12 or newer)
Environment (Other)
No response
The text was updated successfully, but these errors were encountered: