hdWalletFromXprv() and getSignerForAddress() for xprv type were deriving addresses directly from the root key (m/N) instead of the standard BIP44 Ethereum path (m/44'/60'/0'/0/N). This caused imported xprv wallets to generate completely wrong addresses. Navigate to the BIP44 Ethereum derivation path before deriving child addresses, matching the behavior of mnemonic-based wallet imports.
83 lines
2.3 KiB
JavaScript
83 lines
2.3 KiB
JavaScript
// Wallet operations: mnemonic generation, HD derivation, signing.
|
|
// All crypto delegated to ethers.js.
|
|
|
|
const { Mnemonic, HDNodeWallet, Wallet } = require("ethers");
|
|
const { DEBUG, DEBUG_MNEMONIC, BIP44_ETH_PATH } = require("./constants");
|
|
|
|
function generateMnemonic() {
|
|
if (DEBUG) return DEBUG_MNEMONIC;
|
|
const m = Mnemonic.fromEntropy(
|
|
globalThis.crypto.getRandomValues(new Uint8Array(16)),
|
|
);
|
|
return m.phrase;
|
|
}
|
|
|
|
function deriveAddressFromXpub(xpub, index) {
|
|
const node = HDNodeWallet.fromExtendedKey(xpub);
|
|
return node.deriveChild(index).address;
|
|
}
|
|
|
|
function hdWalletFromMnemonic(mnemonic) {
|
|
const node = HDNodeWallet.fromPhrase(mnemonic, "", BIP44_ETH_PATH);
|
|
const xpub = node.neuter().extendedKey;
|
|
const firstAddress = node.deriveChild(0).address;
|
|
return { xpub, firstAddress };
|
|
}
|
|
|
|
function hdWalletFromXprv(xprv) {
|
|
const root = HDNodeWallet.fromExtendedKey(xprv);
|
|
if (!root.privateKey) {
|
|
throw new Error("Not an extended private key (xprv).");
|
|
}
|
|
const node = root.derivePath("44'/60'/0'/0");
|
|
const xpub = node.neuter().extendedKey;
|
|
const firstAddress = node.deriveChild(0).address;
|
|
return { xpub, firstAddress };
|
|
}
|
|
|
|
function isValidXprv(key) {
|
|
try {
|
|
const node = HDNodeWallet.fromExtendedKey(key);
|
|
return !!node.privateKey;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function addressFromPrivateKey(key) {
|
|
const w = new Wallet(key);
|
|
return w.address;
|
|
}
|
|
|
|
function getSignerForAddress(walletData, addrIndex, decryptedSecret) {
|
|
if (walletData.type === "hd") {
|
|
const node = HDNodeWallet.fromPhrase(
|
|
decryptedSecret,
|
|
"",
|
|
BIP44_ETH_PATH,
|
|
);
|
|
return node.deriveChild(addrIndex);
|
|
}
|
|
if (walletData.type === "xprv") {
|
|
const root = HDNodeWallet.fromExtendedKey(decryptedSecret);
|
|
const node = root.derivePath("44'/60'/0'/0");
|
|
return node.deriveChild(addrIndex);
|
|
}
|
|
return new Wallet(decryptedSecret);
|
|
}
|
|
|
|
function isValidMnemonic(mnemonic) {
|
|
return Mnemonic.isValidMnemonic(mnemonic);
|
|
}
|
|
|
|
module.exports = {
|
|
generateMnemonic,
|
|
deriveAddressFromXpub,
|
|
hdWalletFromMnemonic,
|
|
hdWalletFromXprv,
|
|
isValidXprv,
|
|
addressFromPrivateKey,
|
|
getSignerForAddress,
|
|
isValidMnemonic,
|
|
};
|