Skip to content

Commit

Permalink
dry out describeETHPath
Browse files Browse the repository at this point in the history
  • Loading branch information
mrnerdhair committed Feb 12, 2022
1 parent 294331b commit f9c8513
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 180 deletions.
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
97 changes: 97 additions & 0 deletions packages/hdwallet-core/src/ethereum.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { bip32ToAddressNList } from ".";
import { describeETHPath, ETHAddressDerivationScheme } from "./ethereum";

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

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

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

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

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

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

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

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

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

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

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

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

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

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

expect(test("m/44'/60'/1'/0/1")).toMatchInlineSnapshot(`"m/44'/60'/1'/0/1"`);
expect(test("m/44'/60'/1'/2/3")).toMatchInlineSnapshot(`"m/44'/60'/1'/2/3"`);
expect(test("m/44'/60'/0'/0")).toMatchInlineSnapshot(`"Ethereum Account #0"`);
expect(test("m/44'/60'/0'/1")).toMatchInlineSnapshot(`"Ethereum Account #1"`);
expect(test("m/44'/60'/0'/2")).toMatchInlineSnapshot(`"Ethereum Account #2"`);
expect(test("m/44'/60'/0'/3")).toMatchInlineSnapshot(`"Ethereum Account #3"`);
expect(test("m/44'/60'/1'/0")).toMatchInlineSnapshot(`"m/44'/60'/1'/0"`);
expect(test("m/44'/60'/1'/2")).toMatchInlineSnapshot(`"m/44'/60'/1'/2"`);
});
});
36 changes: 28 additions & 8 deletions packages/hdwallet-core/src/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,30 +141,50 @@ 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"
}

export function describeETHPath(path: BIP32Path, addressDerivationScheme = ETHAddressDerivationScheme.BIP44): PathDescription {
let pathStr = addressNListToBIP32(path);
let 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;
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) 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) return unknown;
accountIdx = path[3];
}

let index = path[2] & 0x7fffffff;
return {
verbose: `Ethereum Account #${index}`,
accountIdx: index,
verbose: `Ethereum Account #${accountIdx}`,
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 {
let pathStr = core.addressNListToBIP32(path);
let 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;

let 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 {
let pathStr = core.addressNListToBIP32(path);
let unknown: core.PathDescription = {
Expand Down Expand Up @@ -363,7 +332,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 @@ -402,7 +371,7 @@ export class KeepKeyHDWalletInfo

public ethNextAccountPath(msg: core.ETHAccountPath): core.ETHAccountPath | undefined {
let addressNList = msg.hardenedPath.concat(msg.relPath);
let description = describeETHPath(addressNList);
let 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 {
let pathStr = core.addressNListToBIP32(path);
let 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 @@ -136,7 +93,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, msg.path.length === 5 ? core.ETHAddressDerivationScheme.BIP44 : core.ETHAddressDerivationScheme.OldLedger);
default:
return core.describeUTXOPath(msg.path, msg.coin, msg.scriptType);
}
Expand Down Expand Up @@ -167,7 +124,7 @@ export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo

public ethNextAccountPath(msg: core.ETHAccountPath): core.ETHAccountPath | undefined {
let addressNList = msg.hardenedPath.concat(msg.relPath);
let description = describeETHPath(addressNList);
let description = core.describeETHPath(addressNList, addressNList.length === 5 ? core.ETHAddressDerivationScheme.BIP44 : core.ETHAddressDerivationScheme.OldLedger)
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 {
let pathStr = core.addressNListToBIP32(path);
let 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;

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

export async function ethVerifyMessage(msg: core.ETHVerifyMessage, ethereum: any): Promise<boolean | null> {
console.error("Method ethVerifyMessage unsupported for MetaMask wallet!");
return null;
Expand Down
30 changes: 0 additions & 30 deletions packages/hdwallet-portis/src/ethereum.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,5 @@
import * as core from "@shapeshiftoss/hdwallet-core";

export function describeETHPath(path: core.BIP32Path): core.PathDescription {
let pathStr = core.addressNListToBIP32(path);
let 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;

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

export async function ethVerifyMessage(msg: core.ETHVerifyMessage, web3: any): Promise<boolean> {
const signingAddress = await web3.eth.accounts.recover(msg.message, "0x" + msg.signature, false);
return signingAddress === msg.address;
Expand Down
2 changes: 1 addition & 1 deletion packages/hdwallet-portis/src/portis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ export class PortisHDWalletInfo implements core.HDWalletInfo, core.ETHWalletInfo
public describePath(msg: core.DescribePath): core.PathDescription {
switch (msg.coin) {
case "Ethereum":
return eth.describeETHPath(msg.path);
return core.describeETHPath(msg.path);
case "Bitcoin":
return core.describeUTXOPath(msg.path, msg.coin, msg.scriptType);
default:
Expand Down
Loading

0 comments on commit f9c8513

Please sign in to comment.