Skip to content

Commit

Permalink
refactor: dry out describeETHPath
Browse files Browse the repository at this point in the history
  • Loading branch information
mrnerdhair committed Apr 7, 2022
1 parent 0aadc13 commit 98eba36
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 183 deletions.
2 changes: 1 addition & 1 deletion integration/src/wallets/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ export function selfTest(get: () => core.HDWallet): void {
coin: "Ethereum",
})
).toEqual({
verbose: "Ethereum Account #42",
verbose: "Ethereum Account #42 (Legacy)",
coin: "Ethereum",
isKnown: true,
wholeAccount: true,
Expand Down
2 changes: 2 additions & 0 deletions integration/src/wallets/metamask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export function selfTest(get: () => core.HDWallet): void {
verbose: "Ethereum Account #0",
coin: "Ethereum",
isKnown: true,
isPrefork: false,
accountIdx: 0,
wholeAccount: true,
});
Expand All @@ -103,6 +104,7 @@ export function selfTest(get: () => core.HDWallet): void {
verbose: "Ethereum Account #3",
coin: "Ethereum",
isKnown: true,
isPrefork: false,
accountIdx: 3,
wholeAccount: true,
});
Expand Down
132 changes: 132 additions & 0 deletions packages/hdwallet-core/src/ethereum.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { bip32ToAddressNList } from ".";
import { describeETHPath, ETHAddressDerivationScheme } from "./ethereum";

describe("describeETHPath", () => {
it("works with BIP44 derivation", async () => {
const describePath = (x: string) =>
describeETHPath(bip32ToAddressNList(x), ETHAddressDerivationScheme.BIP44).verbose;

expect(describePath("m/1/2/3")).toMatchInlineSnapshot(`"m/1/2/3"`);
expect(describePath("m/44'/123")).toMatchInlineSnapshot(`"m/44'/123"`);
expect(describePath("m/44'/123'")).toMatchInlineSnapshot(`"m/44'/123'"`);
expect(describePath("m/44'/123'/0")).toMatchInlineSnapshot(`"m/44'/123'/0"`);
expect(describePath("m/44'/123'/0'")).toMatchInlineSnapshot(`"m/44'/123'/0'"`);
expect(describePath("m/44'/123'/0'/0")).toMatchInlineSnapshot(`"m/44'/123'/0'/0"`);
expect(describePath("m/44'/123'/0'/0/0")).toMatchInlineSnapshot(`"m/44'/123'/0'/0/0"`);
expect(describePath("m/44'/0'/0'/0/0")).toMatchInlineSnapshot(`"m/44'/0'/0'/0/0"`);

expect(describePath("m/44'/60'/0'/0/0")).toMatchInlineSnapshot(`"Ethereum Account #0"`);
expect(describePath("m/44'/60'/1'/0/0")).toMatchInlineSnapshot(`"Ethereum Account #1"`);
expect(describePath("m/44'/60'/2'/0/0")).toMatchInlineSnapshot(`"Ethereum Account #2"`);
expect(describePath("m/44'/60'/3'/0/0")).toMatchInlineSnapshot(`"Ethereum Account #3"`);

expect(describePath("m/44'/60'/0'/0/1")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/1"`);
expect(describePath("m/44'/60'/0'/0/2")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/2"`);
expect(describePath("m/44'/60'/0'/0/3")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/3"`);

expect(describePath("m/44'/60'/1'/0/1")).toMatchInlineSnapshot(`"m/44'/60'/1'/0/1"`);
expect(describePath("m/44'/60'/1'/2/3")).toMatchInlineSnapshot(`"m/44'/60'/1'/2/3"`);
expect(describePath("m/44'/60'/0'/0")).toMatchInlineSnapshot(`"m/44'/60'/0'/0"`);
expect(describePath("m/44'/60'/0'/1")).toMatchInlineSnapshot(`"m/44'/60'/0'/1"`);
expect(describePath("m/44'/60'/0'/2")).toMatchInlineSnapshot(`"m/44'/60'/0'/2"`);
expect(describePath("m/44'/60'/0'/3")).toMatchInlineSnapshot(`"m/44'/60'/0'/3"`);
expect(describePath("m/44'/60'/1'/0")).toMatchInlineSnapshot(`"m/44'/60'/1'/0"`);
expect(describePath("m/44'/60'/1'/2")).toMatchInlineSnapshot(`"m/44'/60'/1'/2"`);
});

it("works with Metamask derivation", async () => {
const describePath = (x: string) =>
describeETHPath(bip32ToAddressNList(x), ETHAddressDerivationScheme.Metamask).verbose;

expect(describePath("m/1/2/3")).toMatchInlineSnapshot(`"m/1/2/3"`);
expect(describePath("m/44'/123")).toMatchInlineSnapshot(`"m/44'/123"`);
expect(describePath("m/44'/123'")).toMatchInlineSnapshot(`"m/44'/123'"`);
expect(describePath("m/44'/123'/0")).toMatchInlineSnapshot(`"m/44'/123'/0"`);
expect(describePath("m/44'/123'/0'")).toMatchInlineSnapshot(`"m/44'/123'/0'"`);
expect(describePath("m/44'/123'/0'/0")).toMatchInlineSnapshot(`"m/44'/123'/0'/0"`);
expect(describePath("m/44'/123'/0'/0/0")).toMatchInlineSnapshot(`"m/44'/123'/0'/0/0"`);
expect(describePath("m/44'/0'/0'/0/0")).toMatchInlineSnapshot(`"m/44'/0'/0'/0/0"`);

expect(describePath("m/44'/60'/0'/0/0")).toMatchInlineSnapshot(`"Ethereum Account #0"`);
expect(describePath("m/44'/60'/1'/0/0")).toMatchInlineSnapshot(`"m/44'/60'/1'/0/0"`);
expect(describePath("m/44'/60'/2'/0/0")).toMatchInlineSnapshot(`"m/44'/60'/2'/0/0"`);
expect(describePath("m/44'/60'/3'/0/0")).toMatchInlineSnapshot(`"m/44'/60'/3'/0/0"`);

expect(describePath("m/44'/60'/0'/0/1")).toMatchInlineSnapshot(`"Ethereum Account #1"`);
expect(describePath("m/44'/60'/0'/0/2")).toMatchInlineSnapshot(`"Ethereum Account #2"`);
expect(describePath("m/44'/60'/0'/0/3")).toMatchInlineSnapshot(`"Ethereum Account #3"`);

expect(describePath("m/44'/60'/1'/0/1")).toMatchInlineSnapshot(`"m/44'/60'/1'/0/1"`);
expect(describePath("m/44'/60'/1'/2/3")).toMatchInlineSnapshot(`"m/44'/60'/1'/2/3"`);
expect(describePath("m/44'/60'/0'/0")).toMatchInlineSnapshot(`"m/44'/60'/0'/0"`);
expect(describePath("m/44'/60'/0'/1")).toMatchInlineSnapshot(`"m/44'/60'/0'/1"`);
expect(describePath("m/44'/60'/0'/2")).toMatchInlineSnapshot(`"m/44'/60'/0'/2"`);
expect(describePath("m/44'/60'/0'/3")).toMatchInlineSnapshot(`"m/44'/60'/0'/3"`);
expect(describePath("m/44'/60'/1'/0")).toMatchInlineSnapshot(`"m/44'/60'/1'/0"`);
expect(describePath("m/44'/60'/1'/2")).toMatchInlineSnapshot(`"m/44'/60'/1'/2"`);
});

it("works with OldLedger derivation", async () => {
const describePath = (x: string) =>
describeETHPath(bip32ToAddressNList(x), ETHAddressDerivationScheme.OldLedger).verbose;

expect(describePath("m/1/2/3")).toMatchInlineSnapshot(`"m/1/2/3"`);
expect(describePath("m/44'/123")).toMatchInlineSnapshot(`"m/44'/123"`);
expect(describePath("m/44'/123'")).toMatchInlineSnapshot(`"m/44'/123'"`);
expect(describePath("m/44'/123'/0")).toMatchInlineSnapshot(`"m/44'/123'/0"`);
expect(describePath("m/44'/123'/0'")).toMatchInlineSnapshot(`"m/44'/123'/0'"`);
expect(describePath("m/44'/123'/0'/0")).toMatchInlineSnapshot(`"m/44'/123'/0'/0"`);
expect(describePath("m/44'/123'/0'/0/0")).toMatchInlineSnapshot(`"m/44'/123'/0'/0/0"`);
expect(describePath("m/44'/0'/0'/0/0")).toMatchInlineSnapshot(`"m/44'/0'/0'/0/0"`);

expect(describePath("m/44'/60'/0'/0/0")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/0"`);
expect(describePath("m/44'/60'/1'/0/0")).toMatchInlineSnapshot(`"m/44'/60'/1'/0/0"`);
expect(describePath("m/44'/60'/2'/0/0")).toMatchInlineSnapshot(`"m/44'/60'/2'/0/0"`);
expect(describePath("m/44'/60'/3'/0/0")).toMatchInlineSnapshot(`"m/44'/60'/3'/0/0"`);

expect(describePath("m/44'/60'/0'/0/1")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/1"`);
expect(describePath("m/44'/60'/0'/0/2")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/2"`);
expect(describePath("m/44'/60'/0'/0/3")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/3"`);

expect(describePath("m/44'/60'/1'/0/1")).toMatchInlineSnapshot(`"m/44'/60'/1'/0/1"`);
expect(describePath("m/44'/60'/1'/2/3")).toMatchInlineSnapshot(`"m/44'/60'/1'/2/3"`);
expect(describePath("m/44'/60'/0'/0")).toMatchInlineSnapshot(`"Ethereum Account #0"`);
expect(describePath("m/44'/60'/0'/1")).toMatchInlineSnapshot(`"Ethereum Account #1"`);
expect(describePath("m/44'/60'/0'/2")).toMatchInlineSnapshot(`"Ethereum Account #2"`);
expect(describePath("m/44'/60'/0'/3")).toMatchInlineSnapshot(`"Ethereum Account #3"`);
expect(describePath("m/44'/60'/1'/0")).toMatchInlineSnapshot(`"m/44'/60'/1'/0"`);
expect(describePath("m/44'/60'/1'/2")).toMatchInlineSnapshot(`"m/44'/60'/1'/2"`);
});

it("works with Ledger derivation", async () => {
const describePath = (x: string) =>
describeETHPath(bip32ToAddressNList(x), ETHAddressDerivationScheme.Ledger).verbose;

expect(describePath("m/1/2/3")).toMatchInlineSnapshot(`"m/1/2/3"`);
expect(describePath("m/44'/123")).toMatchInlineSnapshot(`"m/44'/123"`);
expect(describePath("m/44'/123'")).toMatchInlineSnapshot(`"m/44'/123'"`);
expect(describePath("m/44'/123'/0")).toMatchInlineSnapshot(`"m/44'/123'/0"`);
expect(describePath("m/44'/123'/0'")).toMatchInlineSnapshot(`"m/44'/123'/0'"`);
expect(describePath("m/44'/123'/0'/0")).toMatchInlineSnapshot(`"m/44'/123'/0'/0"`);
expect(describePath("m/44'/123'/0'/0/0")).toMatchInlineSnapshot(`"m/44'/123'/0'/0/0"`);
expect(describePath("m/44'/0'/0'/0/0")).toMatchInlineSnapshot(`"m/44'/0'/0'/0/0"`);

expect(describePath("m/44'/60'/0'/0/0")).toMatchInlineSnapshot(`"Ethereum Account #0"`);
expect(describePath("m/44'/60'/1'/0/0")).toMatchInlineSnapshot(`"Ethereum Account #1"`);
expect(describePath("m/44'/60'/2'/0/0")).toMatchInlineSnapshot(`"Ethereum Account #2"`);
expect(describePath("m/44'/60'/3'/0/0")).toMatchInlineSnapshot(`"Ethereum Account #3"`);

expect(describePath("m/44'/60'/0'/0/1")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/1"`);
expect(describePath("m/44'/60'/0'/0/2")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/2"`);
expect(describePath("m/44'/60'/0'/0/3")).toMatchInlineSnapshot(`"m/44'/60'/0'/0/3"`);

expect(describePath("m/44'/60'/1'/0/1")).toMatchInlineSnapshot(`"m/44'/60'/1'/0/1"`);
expect(describePath("m/44'/60'/1'/2/3")).toMatchInlineSnapshot(`"m/44'/60'/1'/2/3"`);
expect(describePath("m/44'/60'/0'/0")).toMatchInlineSnapshot(`"Ethereum Account #0 (Legacy)"`);
expect(describePath("m/44'/60'/0'/1")).toMatchInlineSnapshot(`"Ethereum Account #1 (Legacy)"`);
expect(describePath("m/44'/60'/0'/2")).toMatchInlineSnapshot(`"Ethereum Account #2 (Legacy)"`);
expect(describePath("m/44'/60'/0'/3")).toMatchInlineSnapshot(`"Ethereum Account #3 (Legacy)"`);
expect(describePath("m/44'/60'/1'/0")).toMatchInlineSnapshot(`"m/44'/60'/1'/0"`);
expect(describePath("m/44'/60'/1'/2")).toMatchInlineSnapshot(`"m/44'/60'/1'/2"`);
});
});
57 changes: 48 additions & 9 deletions packages/hdwallet-core/src/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,30 +141,69 @@ export interface ETHWallet extends ETHWalletInfo, HDWallet {
ethVerifyMessage(msg: ETHVerifyMessage): Promise<boolean | null>;
}

export function describeETHPath(path: BIP32Path): PathDescription {
export enum ETHAddressDerivationScheme {
BIP44 = "bip44",
Metamask = "metamask",
OldLedger = "oldledger",
Ledger = "ledger",
}

export function describeETHPath(
path: BIP32Path,
addressDerivationScheme = ETHAddressDerivationScheme.BIP44
): PathDescription {
const pathStr = addressNListToBIP32(path);
const unknown: PathDescription = {
verbose: pathStr,
coin: "Ethereum",
isKnown: false,
};

if (path.length !== 5) return unknown;
if (path.length !== 5 && path.length !== 4) return unknown;

if (path[0] !== 0x80000000 + 44) return unknown;

if (path[1] !== 0x80000000 + slip44ByCoin("Ethereum")) return unknown;

if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown;

if (path[3] !== 0) return unknown;

if (path[4] !== 0) return unknown;

const index = path[2] & 0x7fffffff;
const attributes: string[] = [];
let accountIdx: number;
if (path.length === 5) {
if (path[3] !== 0) return unknown;
if ((path[4] & 0x80000000) !== 0) return unknown;
if (path[4] !== 0 || addressDerivationScheme === ETHAddressDerivationScheme.Metamask) {
if (path[2] !== 0x80000000) return unknown;
if (addressDerivationScheme !== ETHAddressDerivationScheme.Metamask) return unknown;
accountIdx = path[4];
} else {
if (
!(
addressDerivationScheme === ETHAddressDerivationScheme.BIP44 ||
addressDerivationScheme === ETHAddressDerivationScheme.Ledger
)
)
return unknown;
accountIdx = path[2] & 0x7fffffff;
}
} else {
if (path[2] !== 0x80000000) return unknown;
if ((path[3] & 0x80000000) >>> 0 === 0x80000000) return unknown;
if (
!(
addressDerivationScheme === ETHAddressDerivationScheme.OldLedger ||
addressDerivationScheme === ETHAddressDerivationScheme.Ledger
)
)
return unknown;
if (addressDerivationScheme === ETHAddressDerivationScheme.Ledger) attributes.push("Legacy");
accountIdx = path[3];
}

const attr = attributes.length ? ` (${attributes.join(", ")})` : "";
return {
verbose: `Ethereum Account #${index}`,
accountIdx: index,
verbose: `Ethereum Account #${accountIdx}${attr}`,
accountIdx,
wholeAccount: true,
coin: "Ethereum",
isKnown: true,
Expand Down
35 changes: 2 additions & 33 deletions packages/hdwallet-keepkey/src/keepkey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,6 @@ export function isKeepKey(wallet: core.HDWallet): wallet is KeepKeyHDWallet {
return _.isObject(wallet) && (wallet as any)._isKeepKey;
}

function describeETHPath(path: core.BIP32Path): core.PathDescription {
const pathStr = core.addressNListToBIP32(path);
const unknown: core.PathDescription = {
verbose: pathStr,
coin: "Ethereum",
isKnown: false,
};

if (path.length != 5) return unknown;

if (path[0] != 0x80000000 + 44) return unknown;

if (path[1] != 0x80000000 + core.slip44ByCoin("Ethereum")) return unknown;

if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown;

if (path[3] != 0) return unknown;

if (path[4] != 0) return unknown;

const index = path[2] & 0x7fffffff;
return {
verbose: `Ethereum Account #${index}`,
accountIdx: index,
wholeAccount: true,
coin: "Ethereum",
isKnown: true,
isPrefork: false,
};
}

function describeCosmosPath(path: core.BIP32Path): core.PathDescription {
const pathStr = core.addressNListToBIP32(path);
const unknown: core.PathDescription = {
Expand Down Expand Up @@ -364,7 +333,7 @@ export class KeepKeyHDWalletInfo
public describePath(msg: core.DescribePath): core.PathDescription {
switch (msg.coin) {
case "Ethereum":
return describeETHPath(msg.path);
return core.describeETHPath(msg.path);
case "Atom":
return describeCosmosPath(msg.path);
case "Binance":
Expand Down Expand Up @@ -403,7 +372,7 @@ export class KeepKeyHDWalletInfo

public ethNextAccountPath(msg: core.ETHAccountPath): core.ETHAccountPath | undefined {
const addressNList = msg.hardenedPath.concat(msg.relPath);
const description = describeETHPath(addressNList);
const description = core.describeETHPath(addressNList);
if (!description.isKnown) {
return undefined;
}
Expand Down
47 changes: 2 additions & 45 deletions packages/hdwallet-ledger/src/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,6 @@ export function isLedger(wallet: core.HDWallet): wallet is LedgerHDWallet {
return _.isObject(wallet) && (wallet as any)._isLedger;
}

function describeETHPath(path: core.BIP32Path): core.PathDescription {
const pathStr = core.addressNListToBIP32(path);
const unknown: core.PathDescription = {
verbose: pathStr,
coin: "Ethereum",
isKnown: false,
};

if (path.length !== 5 && path.length !== 4) return unknown;

if (path[0] !== 0x80000000 + 44) return unknown;

if (path[1] !== 0x80000000 + core.slip44ByCoin("Ethereum")) return unknown;

if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown;

let accountIdx;
if (path.length === 5) {
if (path[3] !== 0) return unknown;

if (path[4] !== 0) return unknown;

accountIdx = (path[2] & 0x7fffffff) >>> 0;
} else if (path.length === 4) {
if (path[2] !== 0x80000000) return unknown;

if ((path[3] & 0x80000000) >>> 0 === 0x80000000) return unknown;

accountIdx = path[3];
} else {
return unknown;
}

return {
verbose: `Ethereum Account #${accountIdx}`,
wholeAccount: true,
accountIdx,
coin: "Ethereum",
isKnown: true,
isPrefork: false,
};
}

export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo, core.ETHWalletInfo {
readonly _supportsBTCInfo = true;
readonly _supportsETHInfo = true;
Expand Down Expand Up @@ -137,7 +94,7 @@ export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo
public describePath(msg: core.DescribePath): core.PathDescription {
switch (msg.coin) {
case "Ethereum":
return describeETHPath(msg.path);
return core.describeETHPath(msg.path, core.ETHAddressDerivationScheme.Ledger);
default:
return core.describeUTXOPath(msg.path, msg.coin, msg.scriptType);
}
Expand Down Expand Up @@ -168,7 +125,7 @@ export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo

public ethNextAccountPath(msg: core.ETHAccountPath): core.ETHAccountPath | undefined {
const addressNList = msg.hardenedPath.concat(msg.relPath);
const description = describeETHPath(addressNList);
const description = core.describeETHPath(addressNList, core.ETHAddressDerivationScheme.Ledger);
if (!description.isKnown) {
return undefined;
}
Expand Down
30 changes: 0 additions & 30 deletions packages/hdwallet-metamask/src/ethereum.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,6 @@
import * as core from "@shapeshiftoss/hdwallet-core";
import { ETHSignedMessage } from "@shapeshiftoss/hdwallet-core";

export function describeETHPath(path: core.BIP32Path): core.PathDescription {
const pathStr = core.addressNListToBIP32(path);
const unknown: core.PathDescription = {
verbose: pathStr,
coin: "Ethereum",
isKnown: false,
};

if (path.length !== 5) return unknown;

if (path[0] !== 0x80000000 + 44) return unknown;

if (path[1] !== 0x80000000 + core.slip44ByCoin("Ethereum")) return unknown;

if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown;

if (path[3] !== 0) return unknown;

if (path[4] !== 0) return unknown;

const index = path[2] & 0x7fffffff;
return {
verbose: `Ethereum Account #${index}`,
accountIdx: index,
wholeAccount: true,
coin: "Ethereum",
isKnown: true,
};
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function ethVerifyMessage(msg: core.ETHVerifyMessage, ethereum: any): Promise<boolean | null> {
console.error("Method ethVerifyMessage unsupported for MetaMask wallet!");
Expand Down
Loading

0 comments on commit 98eba36

Please sign in to comment.