diff --git a/integration/src/thorchain/thorchain.ts b/integration/src/thorchain/thorchain.ts index be1dbbbe9..1aac7bfd4 100644 --- a/integration/src/thorchain/thorchain.ts +++ b/integration/src/thorchain/thorchain.ts @@ -52,30 +52,17 @@ export function thorchainTests(get: () => { wallet: core.HDWallet; info: core.HD coin: "Thorchain", }); - // This is strange, and probably wrong, behavior... but it's what happens. - if (wallet.getVendor() === "KeepKey") { - // eslint-disable-next-line jest/no-conditional-expect - expect(out).toMatchInlineSnapshot(` + // eslint-disable-next-line jest/no-conditional-expect + expect(out).toMatchInlineSnapshot(` Object { - "coin": "Thorchain", - "isKnown": false, - "scriptType": undefined, - "verbose": "m/44'/931'/0'/0/0", + "accountIdx": 0, + "coin": "Rune", + "isKnown": true, + "isPrefork": false, + "verbose": "THORChain Account #0", + "wholeAccount": true, } - `); - } else { - // eslint-disable-next-line jest/no-conditional-expect - expect(out).toMatchInlineSnapshot(` - Object { - "accountIdx": 0, - "coin": "Thorchain", - "isKnown": true, - "isPrefork": false, - "verbose": "Thorchain Account #0", - "wholeAccount": true, - } - `); - } + `); }, TIMEOUT ); diff --git a/integration/src/wallets/ledger.ts b/integration/src/wallets/ledger.ts index 234872274..1cf343c00 100644 --- a/integration/src/wallets/ledger.ts +++ b/integration/src/wallets/ledger.ts @@ -410,10 +410,15 @@ export function selfTest(get: () => core.HDWallet): void { scriptType: core.BTCInputScriptType.SpendAddress, }) ).toEqual({ - verbose: "m/44'/0'/7'/1/5", + accountIdx: 7, + addressIdx: 5, + verbose: "BitcoinCash Account #7, Change Address #5 (Prefork)", coin: "BitcoinCash", scriptType: core.BTCInputScriptType.SpendAddress, - isKnown: false, + isChange: true, + isKnown: true, + isPrefork: true, + wholeAccount: false, }); expect( @@ -450,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, diff --git a/integration/src/wallets/metamask.ts b/integration/src/wallets/metamask.ts index caae2f682..f151b3043 100644 --- a/integration/src/wallets/metamask.ts +++ b/integration/src/wallets/metamask.ts @@ -113,6 +113,7 @@ export function selfTest(get: () => core.HDWallet): void { verbose: "Ethereum Account #0", coin: "Ethereum", isKnown: true, + isPrefork: false, accountIdx: 0, wholeAccount: true, }); @@ -126,6 +127,7 @@ export function selfTest(get: () => core.HDWallet): void { verbose: "Ethereum Account #3", coin: "Ethereum", isKnown: true, + isPrefork: false, accountIdx: 3, wholeAccount: true, }); diff --git a/integration/src/wallets/native.ts b/integration/src/wallets/native.ts index 0b24af35b..aa01ec2af 100644 --- a/integration/src/wallets/native.ts +++ b/integration/src/wallets/native.ts @@ -335,7 +335,7 @@ export function selfTest(get: () => core.HDWallet): void { coin: "Litecoin", isKnown: true, scriptType: "p2wpkh", - verbose: "Litecoin Account #4 (Segwit)", + verbose: "Litecoin Account #4 (Segwit Native)", wholeAccount: true, isPrefork: false, }, diff --git a/integration/src/wallets/trezor.ts b/integration/src/wallets/trezor.ts index e8e5b3add..8e5316875 100644 --- a/integration/src/wallets/trezor.ts +++ b/integration/src/wallets/trezor.ts @@ -519,10 +519,15 @@ export function selfTest(get: () => core.HDWallet): void { scriptType: core.BTCInputScriptType.SpendAddress, }) ).toEqual({ - verbose: "m/44'/0'/7'/1/5", + accountIdx: 7, + addressIdx: 5, + verbose: "BitcoinCash Account #7, Change Address #5 (Prefork)", coin: "BitcoinCash", scriptType: core.BTCInputScriptType.SpendAddress, - isKnown: false, + isChange: true, + isKnown: true, + isPrefork: true, + wholeAccount: false, }); expect( diff --git a/packages/hdwallet-core/src/bitcoin.ts b/packages/hdwallet-core/src/bitcoin.ts index 1676c413a..dfd47d936 100644 --- a/packages/hdwallet-core/src/bitcoin.ts +++ b/packages/hdwallet-core/src/bitcoin.ts @@ -381,7 +381,7 @@ export function unknownUTXOPath(path: BIP32Path, coin: Coin, scriptType?: BTCInp }; } -export function describeUTXOPath(path: BIP32Path, coin: Coin, scriptType: BTCInputScriptType): PathDescription { +export function btcDescribePath(path: BIP32Path, coin: Coin, scriptType?: BTCInputScriptType): PathDescription { const unknown = unknownUTXOPath(path, coin, scriptType); if (path.length !== 3 && path.length !== 5) return unknown; @@ -396,16 +396,22 @@ export function describeUTXOPath(path: BIP32Path, coin: Coin, scriptType: BTCInp if (purpose === 49 && scriptType !== BTCInputScriptType.SpendP2SHWitness) return unknown; + if (purpose === 84 && !(scriptType === BTCInputScriptType.SpendWitness || scriptType === BTCInputScriptType.Bech32)) { + return unknown; + } + const wholeAccount = path.length === 3; - const script = ( - { - [BTCInputScriptType.SpendAddress]: ["Legacy"], - [BTCInputScriptType.SpendP2SHWitness]: [], - [BTCInputScriptType.SpendWitness]: ["Segwit"], - [BTCInputScriptType.Bech32]: ["Segwit Native"], - } as Partial> - )[scriptType]; + const script = scriptType + ? ( + { + [BTCInputScriptType.SpendAddress]: ["Legacy"], + [BTCInputScriptType.SpendP2SHWitness]: [], + [BTCInputScriptType.SpendWitness]: ["Segwit Native"], + [BTCInputScriptType.Bech32]: ["Segwit Native"], + } as Partial> + )[scriptType] ?? [] + : []; let isPrefork = false; const slip44 = slip44ByCoin(coin); @@ -438,7 +444,7 @@ export function describeUTXOPath(path: BIP32Path, coin: Coin, scriptType: BTCInp case "Litecoin": case "BitcoinGold": case "Testnet": { - if (script) attributes = attributes.concat(script); + attributes = attributes.concat(script); break; } default: diff --git a/packages/hdwallet-core/src/eos.ts b/packages/hdwallet-core/src/eos.ts index 42548568d..272c519d5 100644 --- a/packages/hdwallet-core/src/eos.ts +++ b/packages/hdwallet-core/src/eos.ts @@ -1,3 +1,4 @@ +import { addressNListToBIP32, PathDescription, slip44ByCoin } from "."; import { BIP32Path, HDWallet, HDWalletInfo } from "./wallet"; export interface EosGetPublicKey { @@ -93,3 +94,42 @@ export interface EosWallet extends EosWalletInfo, HDWallet { eosGetPublicKey(msg: EosGetPublicKey): Promise; eosSignTx(msg: EosToSignTx): Promise; } + +export function eosDescribePath(path: BIP32Path): PathDescription { + const pathStr = addressNListToBIP32(path); + const unknown: PathDescription = { + verbose: pathStr, + coin: "Eos", + isKnown: false, + }; + + if (path.length != 5) { + return unknown; + } + + if (path[0] != 0x80000000 + 44) { + return unknown; + } + + if (path[1] != 0x80000000 + slip44ByCoin("Eos")) { + return unknown; + } + + if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) { + return unknown; + } + + if (path[3] !== 0 || path[4] !== 0) { + return unknown; + } + + const index = path[2] & 0x7fffffff; + return { + verbose: `Eos Account #${index}`, + accountIdx: index, + wholeAccount: true, + coin: "Eos", + isKnown: true, + isPrefork: false, + }; +} diff --git a/packages/hdwallet-core/src/ethereum.test.ts b/packages/hdwallet-core/src/ethereum.test.ts new file mode 100644 index 000000000..1a3eb81d8 --- /dev/null +++ b/packages/hdwallet-core/src/ethereum.test.ts @@ -0,0 +1,132 @@ +import { bip32ToAddressNList } from "."; +import { ETHAddressDerivationScheme, ethDescribePath } from "./ethereum"; + +describe("ethDescribePath", () => { + it("works with BIP44 derivation", async () => { + const describePath = (x: string) => + ethDescribePath(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) => + ethDescribePath(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) => + ethDescribePath(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) => + ethDescribePath(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"`); + }); +}); diff --git a/packages/hdwallet-core/src/ethereum.ts b/packages/hdwallet-core/src/ethereum.ts index 5e3a25155..35c86e9cf 100644 --- a/packages/hdwallet-core/src/ethereum.ts +++ b/packages/hdwallet-core/src/ethereum.ts @@ -149,7 +149,17 @@ export interface ETHWallet extends ETHWalletInfo, HDWallet { ethVerifyMessage(msg: ETHVerifyMessage): Promise; } -export function describeETHPath(path: BIP32Path): PathDescription { +export enum ETHAddressDerivationScheme { + BIP44 = "bip44", + Metamask = "metamask", + OldLedger = "oldledger", + Ledger = "ledger", +} + +export function ethDescribePath( + path: BIP32Path, + addressDerivationScheme = ETHAddressDerivationScheme.BIP44 +): PathDescription { const pathStr = addressNListToBIP32(path); const unknown: PathDescription = { verbose: pathStr, @@ -157,7 +167,7 @@ export function describeETHPath(path: BIP32Path): PathDescription { isKnown: false, }; - if (path.length !== 5) return unknown; + if (path.length !== 5 && path.length !== 4) return unknown; if (path[0] !== 0x80000000 + 44) return unknown; @@ -165,14 +175,43 @@ export function describeETHPath(path: BIP32Path): PathDescription { 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, diff --git a/packages/hdwallet-core/src/ripple.ts b/packages/hdwallet-core/src/ripple.ts index 41d437f6c..b78829a3a 100644 --- a/packages/hdwallet-core/src/ripple.ts +++ b/packages/hdwallet-core/src/ripple.ts @@ -1,3 +1,4 @@ +import { addressNListToBIP32, PathDescription, slip44ByCoin } from "."; import { BIP32Path, HDWallet, HDWalletInfo } from "./wallet"; export interface RippleGetAddress { @@ -97,3 +98,42 @@ export interface RippleWallet extends RippleWalletInfo, HDWallet { rippleGetAddress(msg: RippleGetAddress): Promise; rippleSignTx(msg: RippleSignTx): Promise; } + +export function rippleDescribePath(path: BIP32Path): PathDescription { + const pathStr = addressNListToBIP32(path); + const unknown: PathDescription = { + verbose: pathStr, + coin: "Ripple", + isKnown: false, + }; + + if (path.length != 5) { + return unknown; + } + + if (path[0] != 0x80000000 + 44) { + return unknown; + } + + if (path[1] != 0x80000000 + slip44ByCoin("Ripple")) { + return unknown; + } + + if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) { + return unknown; + } + + if (path[3] !== 0 || path[4] !== 0) { + return unknown; + } + + const index = path[2] & 0x7fffffff; + return { + verbose: `Ripple Account #${index}`, + accountIdx: index, + wholeAccount: true, + coin: "Ripple", + isKnown: true, + isPrefork: false, + }; +} diff --git a/packages/hdwallet-core/src/thorchain.ts b/packages/hdwallet-core/src/thorchain.ts index 66a4bcd87..5220b07cd 100644 --- a/packages/hdwallet-core/src/thorchain.ts +++ b/packages/hdwallet-core/src/thorchain.ts @@ -126,10 +126,10 @@ export function thorchainDescribePath(path: BIP32Path): PathDescription { const index = path[2] & 0x7fffffff; return { - verbose: `Thorchain Account #${index}`, + verbose: `THORChain Account #${index}`, accountIdx: index, wholeAccount: true, - coin: "Thorchain", + coin: "Rune", isKnown: true, isPrefork: false, }; diff --git a/packages/hdwallet-core/src/wallet.ts b/packages/hdwallet-core/src/wallet.ts index 3ed2ec9b7..da3b832ed 100644 --- a/packages/hdwallet-core/src/wallet.ts +++ b/packages/hdwallet-core/src/wallet.ts @@ -1,5 +1,20 @@ import _ from "lodash"; +import { + binanceDescribePath, + btcDescribePath, + cosmosDescribePath, + eosDescribePath, + ETHAddressDerivationScheme, + ethDescribePath, + fioDescribePath, + kavaDescribePath, + osmosisDescribePath, + rippleDescribePath, + secretDescribePath, + terraDescribePath, + thorchainDescribePath, +} from "."; import { BinanceWallet, BinanceWalletInfo } from "./binance"; import { BTCInputScriptType, BTCWallet, BTCWalletInfo } from "./bitcoin"; import { CosmosWallet, CosmosWalletInfo } from "./cosmos"; @@ -385,3 +400,43 @@ export interface HDWallet extends HDWalletInfo { */ disconnect(): Promise; } + +export function describePath( + msg: DescribePath, + ethAddressDerivationScheme?: ETHAddressDerivationScheme +): PathDescription { + switch (msg.coin.toLowerCase()) { + case "ethereum": + return ethDescribePath(msg.path, ethAddressDerivationScheme); + case "atom": + return cosmosDescribePath(msg.path); + case "rune": + case "trune": + case "thorchain": + return thorchainDescribePath(msg.path); + case "secret": + case "scrt": + case "tscrt": + return secretDescribePath(msg.path); + case "luna": + case "terra": + case "tluna": + return terraDescribePath(msg.path); + case "kava": + case "tkava": + return kavaDescribePath(msg.path); + case "binance": + return binanceDescribePath(msg.path); + case "osmosis": + case "osmo": + return osmosisDescribePath(msg.path); + case "fio": + return fioDescribePath(msg.path); + case "ripple": + return rippleDescribePath(msg.path); + case "eos": + return eosDescribePath(msg.path); + default: + return btcDescribePath(msg.path, msg.coin, msg.scriptType); + } +} diff --git a/packages/hdwallet-keepkey/src/keepkey.ts b/packages/hdwallet-keepkey/src/keepkey.ts index 19714f3d7..6e61ec30b 100644 --- a/packages/hdwallet-keepkey/src/keepkey.ts +++ b/packages/hdwallet-keepkey/src/keepkey.ts @@ -19,348 +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 describeUTXOPath( - path: core.BIP32Path, - coin: core.Coin, - scriptType?: core.BTCInputScriptType -): core.PathDescription { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin, - scriptType, - isKnown: false, - }; - if (!scriptType) return unknown; - - if (!Btc.btcSupportsCoin(coin)) return unknown; - - if (!Btc.btcSupportsScriptType(coin, scriptType)) return unknown; - - if (path.length !== 3 && path.length !== 5) return unknown; - - if ((path[0] & 0x80000000) >>> 0 !== 0x80000000) return unknown; - - const purpose = path[0] & 0x7fffffff; - - if (![44, 49, 84].includes(purpose)) return unknown; - - if (purpose === 44 && scriptType !== core.BTCInputScriptType.SpendAddress) return unknown; - - if (purpose === 49 && scriptType !== core.BTCInputScriptType.SpendP2SHWitness) return unknown; - - if (purpose === 84 && scriptType !== core.BTCInputScriptType.SpendWitness) return unknown; - - const wholeAccount = path.length === 3; - - const script = scriptType - ? ( - { - [core.BTCInputScriptType.SpendAddress]: ["Legacy"], - [core.BTCInputScriptType.SpendP2SHWitness]: [], - [core.BTCInputScriptType.SpendWitness]: ["Segwit Native"], - } as Partial> - )[scriptType] ?? [] - : []; - - let isPrefork = false; - const slip44 = core.slip44ByCoin(coin); - if (slip44 === undefined) return unknown; - if (path[1] !== 0x80000000 + slip44) { - switch (coin) { - case "BitcoinCash": - case "BitcoinGold": { - if (path[1] === 0x80000000 + core.slip44ByCoin("Bitcoin")) { - isPrefork = true; - break; - } - return unknown; - } - case "BitcoinSV": { - if ( - path[1] === 0x80000000 + core.slip44ByCoin("Bitcoin") || - path[1] === 0x80000000 + core.slip44ByCoin("BitcoinCash") - ) { - isPrefork = true; - break; - } - return unknown; - } - default: - return unknown; - } - } - - let attributes = isPrefork ? ["Prefork"] : []; - switch (coin) { - case "Bitcoin": - case "Litecoin": - case "BitcoinGold": - case "Testnet": { - attributes = attributes.concat(script); - break; - } - default: - break; - } - - const attr = attributes.length ? ` (${attributes.join(", ")})` : ""; - - const accountIdx = path[2] & 0x7fffffff; - - if (wholeAccount) { - return { - coin, - verbose: `${coin} Account #${accountIdx}${attr}`, - accountIdx, - wholeAccount: true, - isKnown: true, - scriptType, - isPrefork, - }; - } else { - const change = path[3] === 1 ? "Change " : ""; - const addressIdx = path[4]; - return { - coin, - verbose: `${coin} Account #${accountIdx}, ${change}Address #${addressIdx}${attr}`, - accountIdx, - addressIdx, - wholeAccount: false, - isKnown: true, - isChange: path[3] === 1, - scriptType, - isPrefork, - }; - } -} - -function describeCosmosPath(path: core.BIP32Path): core.PathDescription { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin: "Atom", - isKnown: false, - }; - - if (path.length != 5) { - return unknown; - } - - if (path[0] != 0x80000000 + 44) { - return unknown; - } - - if (path[1] != 0x80000000 + core.slip44ByCoin("Atom")) { - return unknown; - } - - if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) { - return unknown; - } - - if (path[3] !== 0 || path[4] !== 0) { - return unknown; - } - - const index = path[2] & 0x7fffffff; - return { - verbose: `Cosmos Account #${index}`, - accountIdx: index, - wholeAccount: true, - coin: "Atom", - isKnown: true, - isPrefork: false, - }; -} - -function describeThorchainPath(path: core.BIP32Path): core.PathDescription { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin: "Rune", - isKnown: false, - }; - - if (path.length != 5) { - return unknown; - } - - if (path[0] != 0x80000000 + 44) { - return unknown; - } - - if (path[1] != 0x80000000 + core.slip44ByCoin("Rune")) { - return unknown; - } - - if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) { - return unknown; - } - - if (path[3] !== 0 || path[4] !== 0) { - return unknown; - } - - const index = path[2] & 0x7fffffff; - return { - verbose: `THORChain Account #${index}`, - accountIdx: index, - wholeAccount: true, - coin: "Rune", - isKnown: true, - isPrefork: false, - }; -} - -function describeEosPath(path: core.BIP32Path): core.PathDescription { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin: "Eos", - isKnown: false, - }; - - if (path.length != 5) { - return unknown; - } - - if (path[0] != 0x80000000 + 44) { - return unknown; - } - - if (path[1] != 0x80000000 + core.slip44ByCoin("Eos")) { - return unknown; - } - - if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) { - return unknown; - } - - if (path[3] !== 0 || path[4] !== 0) { - return unknown; - } - - const index = path[2] & 0x7fffffff; - return { - verbose: `Eos Account #${index}`, - accountIdx: index, - wholeAccount: true, - coin: "Eos", - isKnown: true, - isPrefork: false, - }; -} - -function describeRipplePath(path: core.BIP32Path): core.PathDescription { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin: "Ripple", - isKnown: false, - }; - - if (path.length != 5) { - return unknown; - } - - if (path[0] != 0x80000000 + 44) { - return unknown; - } - - if (path[1] != 0x80000000 + core.slip44ByCoin("Ripple")) { - return unknown; - } - - if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) { - return unknown; - } - - if (path[3] !== 0 || path[4] !== 0) { - return unknown; - } - - const index = path[2] & 0x7fffffff; - return { - verbose: `Ripple Account #${index}`, - accountIdx: index, - wholeAccount: true, - coin: "Ripple", - isKnown: true, - isPrefork: false, - }; -} - -function describeBinancePath(path: core.BIP32Path): core.PathDescription { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin: "Binance", - isKnown: false, - }; - - if (path.length != 5) { - return unknown; - } - - if (path[0] != 0x80000000 + 44) { - return unknown; - } - - if (path[1] != 0x80000000 + core.slip44ByCoin("Binance")) { - return unknown; - } - - if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) { - return unknown; - } - - if (path[3] !== 0 || path[4] !== 0) { - return unknown; - } - - const index = path[2] & 0x7fffffff; - return { - verbose: `Binance Account #${index}`, - accountIdx: index, - wholeAccount: true, - coin: "Binance", - isKnown: true, - isPrefork: false, - }; -} - export class KeepKeyHDWalletInfo implements core.HDWalletInfo, @@ -478,24 +136,11 @@ export class KeepKeyHDWalletInfo } public describePath(msg: core.DescribePath): core.PathDescription { - switch (msg.coin) { - case "Ethereum": - return describeETHPath(msg.path); - case "Atom": - return describeCosmosPath(msg.path); - case "Binance": - return describeBinancePath(msg.path); - case "Ripple": - return describeRipplePath(msg.path); - case "Eos": - return describeEosPath(msg.path); - default: - return describeUTXOPath(msg.path, msg.coin, msg.scriptType); - } + return core.describePath(msg); } public btcNextAccountPath(msg: core.BTCAccountPath): core.BTCAccountPath | undefined { - const description = describeUTXOPath(msg.addressNList, msg.coin, msg.scriptType); + const description = core.btcDescribePath(msg.addressNList, msg.coin, msg.scriptType); if (!description.isKnown) { return undefined; } @@ -519,7 +164,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.ethDescribePath(addressNList); if (!description.isKnown) { return undefined; } @@ -538,7 +183,7 @@ export class KeepKeyHDWalletInfo } public cosmosNextAccountPath(msg: core.CosmosAccountPath): core.CosmosAccountPath | undefined { - const description = describeCosmosPath(msg.addressNList); + const description = core.cosmosDescribePath(msg.addressNList); if (!description.isKnown) { return undefined; } @@ -553,7 +198,7 @@ export class KeepKeyHDWalletInfo } public thorchainNextAccountPath(msg: core.ThorchainAccountPath): core.ThorchainAccountPath | undefined { - const description = describeThorchainPath(msg.addressNList); + const description = core.thorchainDescribePath(msg.addressNList); if (!description.isKnown) { return undefined; } @@ -568,7 +213,7 @@ export class KeepKeyHDWalletInfo } public rippleNextAccountPath(msg: core.RippleAccountPath): core.RippleAccountPath | undefined { - const description = describeRipplePath(msg.addressNList); + const description = core.rippleDescribePath(msg.addressNList); if (!description.isKnown) { return undefined; } @@ -582,7 +227,7 @@ export class KeepKeyHDWalletInfo } public binanceNextAccountPath(msg: core.BinanceAccountPath): core.BinanceAccountPath | undefined { - const description = describeBinancePath(msg.addressNList); + const description = core.binanceDescribePath(msg.addressNList); if (!description.isKnown) { return undefined; } @@ -597,7 +242,7 @@ export class KeepKeyHDWalletInfo } public eosNextAccountPath(msg: core.EosAccountPath): core.EosAccountPath | undefined { - const description = describeEosPath(msg.addressNList); + const description = core.eosDescribePath(msg.addressNList); if (!description.isKnown) { return undefined; } diff --git a/packages/hdwallet-ledger/src/ledger.ts b/packages/hdwallet-ledger/src/ledger.ts index 77aa91070..27c9253c4 100644 --- a/packages/hdwallet-ledger/src/ledger.ts +++ b/packages/hdwallet-ledger/src/ledger.ts @@ -10,130 +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, - }; -} - -function describeUTXOPath(path: core.BIP32Path, coin: core.Coin, scriptType?: core.BTCInputScriptType) { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin, - scriptType, - isKnown: false, - }; - - if (!btc.btcSupportsCoin(coin)) return unknown; - - if (!btc.btcSupportsScriptType(coin, scriptType)) return unknown; - - if (path.length !== 3 && path.length !== 5) return unknown; - - if ((path[0] & 0x80000000) >>> 0 !== 0x80000000) return unknown; - - const purpose = path[0] & 0x7fffffff; - - if (![44, 49, 84].includes(purpose)) return unknown; - - if (purpose === 44 && scriptType !== core.BTCInputScriptType.SpendAddress) return unknown; - - if (purpose === 49 && scriptType !== core.BTCInputScriptType.SpendP2SHWitness) return unknown; - - if (purpose === 84 && scriptType !== core.BTCInputScriptType.SpendWitness) return unknown; - - const slip44 = core.slip44ByCoin(coin); - if (slip44 === undefined || path[1] !== 0x80000000 + slip44) return unknown; - - const wholeAccount = path.length === 3; - - let script = scriptType - ? ( - { - [core.BTCInputScriptType.SpendAddress]: " (Legacy)", - [core.BTCInputScriptType.SpendP2SHWitness]: "", - [core.BTCInputScriptType.SpendWitness]: " (Segwit Native)", - } as Partial> - )[scriptType] - : undefined; - - switch (coin) { - case "Bitcoin": - case "Litecoin": - case "BitcoinGold": - case "Testnet": - break; - default: - script = ""; - } - - const accountIdx = path[2] & 0x7fffffff; - - if (wholeAccount) { - return { - verbose: `${coin} Account #${accountIdx}${script}`, - accountIdx, - coin, - scriptType, - wholeAccount: true, - isKnown: true, - isPrefork: false, - }; - } else { - const change = path[3] == 1 ? "Change " : ""; - const addressIdx = path[4]; - return { - verbose: `${coin} Account #${accountIdx}, ${change}Address #${addressIdx}${script}`, - coin, - scriptType, - accountIdx, - addressIdx, - wholeAccount: false, - isChange: path[3] == 1, - isKnown: true, - isPrefork: false, - }; - } -} - export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo, core.ETHWalletInfo { readonly _supportsBTCInfo = true; readonly _supportsETHInfo = true; @@ -216,16 +92,11 @@ export class LedgerHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo } public describePath(msg: core.DescribePath): core.PathDescription { - switch (msg.coin) { - case "Ethereum": - return describeETHPath(msg.path); - default: - return describeUTXOPath(msg.path, msg.coin, msg.scriptType); - } + return core.describePath(msg, core.ETHAddressDerivationScheme.Ledger); } public btcNextAccountPath(msg: core.BTCAccountPath): core.BTCAccountPath | undefined { - const description = describeUTXOPath(msg.addressNList, msg.coin, msg.scriptType); + const description = core.btcDescribePath(msg.addressNList, msg.coin, msg.scriptType); if (!description.isKnown) { return undefined; } @@ -249,7 +120,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.ethDescribePath(addressNList, core.ETHAddressDerivationScheme.Ledger); if (!description.isKnown) { return undefined; } diff --git a/packages/hdwallet-metamask/src/ethereum.ts b/packages/hdwallet-metamask/src/ethereum.ts index a95f11e64..4fd57538c 100644 --- a/packages/hdwallet-metamask/src/ethereum.ts +++ b/packages/hdwallet-metamask/src/ethereum.ts @@ -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 { console.error("Method ethVerifyMessage unsupported for MetaMask wallet!"); diff --git a/packages/hdwallet-metamask/src/metamask.ts b/packages/hdwallet-metamask/src/metamask.ts index 44c4f87d0..2ae6bb4d2 100644 --- a/packages/hdwallet-metamask/src/metamask.ts +++ b/packages/hdwallet-metamask/src/metamask.ts @@ -54,12 +54,7 @@ export class MetaMaskHDWalletInfo implements core.HDWalletInfo, core.ETHWalletIn } public describePath(msg: core.DescribePath): core.PathDescription { - switch (msg.coin) { - case "Ethereum": - return eth.describeETHPath(msg.path); - default: - throw new Error("Unsupported path"); - } + return core.describePath(msg); } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/hdwallet-native/src/bitcoin.test.ts b/packages/hdwallet-native/src/bitcoin.test.ts index e51c9199b..b19c26029 100644 --- a/packages/hdwallet-native/src/bitcoin.test.ts +++ b/packages/hdwallet-native/src/bitcoin.test.ts @@ -286,8 +286,8 @@ describe("NativeBTCWalletInfo", () => { ["BIP84", "m/84'/0'/0'/0/0", "p2wpkh"], ])("should not work for a %s path with an unrecognized purpose field", (_, path, scriptType: any) => { const mock = jest - .spyOn(core, "describeUTXOPath") - .mockReturnValue(core.describeUTXOPath(core.bip32ToAddressNList(path), "Bitcoin", scriptType)); + .spyOn(core, "btcDescribePath") + .mockReturnValue(core.btcDescribePath(core.bip32ToAddressNList(path), "Bitcoin", scriptType)); expect( info.btcNextAccountPath({ coin: "Bitcoin", diff --git a/packages/hdwallet-native/src/bitcoin.ts b/packages/hdwallet-native/src/bitcoin.ts index 182ea6bbd..c471a10b2 100644 --- a/packages/hdwallet-native/src/bitcoin.ts +++ b/packages/hdwallet-native/src/bitcoin.ts @@ -108,7 +108,7 @@ export function MixinNativeBTCWalletInfo { }, { msg: { coin: "rune", path: [44 + 0x80000000, 931 + 0x80000000, 0 + 0x80000000, 0, 0] }, - out: { coin: "Thorchain", verbose: "Thorchain Account #0", isKnown: true }, + out: { coin: "Rune", verbose: "THORChain Account #0", isKnown: true }, }, { msg: { coin: "secret", path: [44 + 0x80000000, 529 + 0x80000000, 0 + 0x80000000, 0, 0] }, @@ -94,8 +94,11 @@ describe("NativeHDWalletInfo", () => { msg: { coin: "Osmo", path: [44 + 0x80000000, 118 + 0x80000000, 0 + 0x80000000, 0, 0] }, out: { coin: "Osmo", verbose: "Osmosis Account #0", isKnown: true }, }, + { + msg: { coin: "foobar", path: [1, 2, 3] }, + out: { coin: "foobar", verbose: "m/1/2/3", isKnown: false }, + }, ].forEach((x) => expect(info.describePath(x.msg)).toMatchObject(x.out)); - expect(() => info.describePath({ coin: "foobar", path: [1, 2, 3] })).toThrowError("Unsupported path"); }); }); diff --git a/packages/hdwallet-native/src/native.ts b/packages/hdwallet-native/src/native.ts index 9f449b0c2..2c506aef9 100644 --- a/packages/hdwallet-native/src/native.ts +++ b/packages/hdwallet-native/src/native.ts @@ -75,7 +75,7 @@ export class NativeHDWalletInfoBase implements core.HDWalletInfo { // eslint-disable-next-line @typescript-eslint/no-unused-vars describePath(msg: core.DescribePath): core.PathDescription { - throw new Error("unreachable"); + return core.describePath(msg); } } @@ -128,56 +128,7 @@ class NativeHDWalletInfo ) ) ) - implements core.HDWalletInfo -{ - describePath(msg: core.DescribePath): core.PathDescription { - switch (msg.coin.toLowerCase()) { - case "bitcoin": - case "bitcoincash": - case "dash": - case "digibyte": - case "dogecoin": - case "litecoin": - case "testnet": { - const unknown = core.unknownUTXOPath(msg.path, msg.coin, msg.scriptType); - - if (!msg.scriptType) return unknown; - if (!super.btcSupportsCoinSync(msg.coin)) return unknown; - if (!super.btcSupportsScriptTypeSync(msg.coin, msg.scriptType)) return unknown; - - return core.describeUTXOPath(msg.path, msg.coin, msg.scriptType); - } - case "ethereum": - return core.describeETHPath(msg.path); - case "atom": - return core.cosmosDescribePath(msg.path); - case "rune": - case "trune": - case "thorchain": - return core.thorchainDescribePath(msg.path); - case "secret": - case "scrt": - case "tscrt": - return core.secretDescribePath(msg.path); - case "luna": - case "terra": - case "tluna": - return core.terraDescribePath(msg.path); - case "kava": - case "tkava": - return core.kavaDescribePath(msg.path); - case "binance": - return core.binanceDescribePath(msg.path); - case "osmosis": - case "osmo": - return core.osmosisDescribePath(msg.path); - case "fio": - return core.fioDescribePath(msg.path); - default: - throw new Error("Unsupported path"); - } - } -} + implements core.HDWalletInfo {} export class NativeHDWallet extends MixinNativeBTCWallet( diff --git a/packages/hdwallet-portis/src/bitcoin.ts b/packages/hdwallet-portis/src/bitcoin.ts index 94128be28..a32a816d1 100644 --- a/packages/hdwallet-portis/src/bitcoin.ts +++ b/packages/hdwallet-portis/src/bitcoin.ts @@ -4,117 +4,6 @@ import * as bip32 from "bip32"; import * as bitcoin from "bitcoinjs-lib"; import * as bitcoinMsg from "bitcoinjs-message"; -export function describeUTXOPath( - path: core.BIP32Path, - coin: core.Coin, - scriptType?: core.BTCInputScriptType -): core.PathDescription { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin, - scriptType, - isKnown: false, - }; - - if (path.length !== 3 && path.length !== 5) return unknown; - - if ((path[0] & 0x80000000) >>> 0 !== 0x80000000) return unknown; - - const purpose = path[0] & 0x7fffffff; - - if (![44, 49, 84].includes(purpose)) return unknown; - - if (purpose === 44 && scriptType !== core.BTCInputScriptType.SpendAddress) return unknown; - - if (purpose === 49 && scriptType !== core.BTCInputScriptType.SpendP2SHWitness) return unknown; - - if (purpose === 84 && scriptType !== core.BTCInputScriptType.SpendWitness) return unknown; - - const wholeAccount = path.length === 3; - - const script = scriptType - ? ( - { - [core.BTCInputScriptType.SpendAddress]: ["Legacy"], - [core.BTCInputScriptType.SpendP2SHWitness]: [], - [core.BTCInputScriptType.SpendWitness]: ["Segwit Native"], - } as Partial> - )[scriptType] ?? ([] as string[]) - : ([] as string[]); - - let isPrefork = false; - const slip44 = core.slip44ByCoin(coin); - if (slip44 === undefined) return unknown; - if (path[1] !== 0x80000000 + slip44) { - switch (coin) { - case "BitcoinCash": - case "BitcoinGold": { - if (path[1] === 0x80000000 + core.slip44ByCoin("Bitcoin")) { - isPrefork = true; - break; - } - return unknown; - } - case "BitcoinSV": { - if ( - path[1] === 0x80000000 + core.slip44ByCoin("Bitcoin") || - path[1] === 0x80000000 + core.slip44ByCoin("BitcoinCash") - ) { - isPrefork = true; - break; - } - return unknown; - } - default: - return unknown; - } - } - - let attributes = isPrefork ? ["Prefork"] : []; - switch (coin) { - case "Bitcoin": - case "Litecoin": - case "BitcoinGold": - case "Testnet": { - attributes = attributes.concat(script); - break; - } - default: - break; - } - - const attr = attributes.length ? ` (${attributes.join(", ")})` : ""; - - const accountIdx = path[2] & 0x7fffffff; - - if (wholeAccount) { - return { - coin, - verbose: `${coin} Account #${accountIdx}${attr}`, - accountIdx, - wholeAccount: true, - isKnown: true, - scriptType, - isPrefork, - }; - } else { - const change = path[3] === 1 ? "Change " : ""; - const addressIdx = path[4]; - return { - coin, - verbose: `${coin} Account #${accountIdx}, ${change}Address #${addressIdx}${attr}`, - accountIdx, - addressIdx, - wholeAccount: false, - isKnown: true, - isChange: path[3] === 1, - scriptType, - isPrefork, - }; - } -} - export function verifyScriptTypePurpose(scriptType: core.BTCInputScriptType, purpose: number): boolean { return ( (purpose === 0x80000000 + 44 && scriptType === core.BTCInputScriptType.SpendAddress) || diff --git a/packages/hdwallet-portis/src/ethereum.ts b/packages/hdwallet-portis/src/ethereum.ts index 09b77a9bf..deba18e0f 100644 --- a/packages/hdwallet-portis/src/ethereum.ts +++ b/packages/hdwallet-portis/src/ethereum.ts @@ -1,35 +1,5 @@ import * as core 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, - }; -} - export async function ethVerifyMessage(msg: core.ETHVerifyMessage, web3: any): Promise { const signingAddress = await web3.eth.accounts.recover(msg.message, "0x" + msg.signature, false); return signingAddress === msg.address; diff --git a/packages/hdwallet-portis/src/portis.ts b/packages/hdwallet-portis/src/portis.ts index 9bee111be..4efa30120 100644 --- a/packages/hdwallet-portis/src/portis.ts +++ b/packages/hdwallet-portis/src/portis.ts @@ -51,14 +51,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); - case "Bitcoin": - return btc.describeUTXOPath(msg.path, msg.coin, msg.scriptType); - default: - throw new Error("Unsupported path"); - } + return core.describePath(msg); } public async btcSupportsCoin(coin: core.Coin): Promise { diff --git a/packages/hdwallet-trezor/src/trezor.ts b/packages/hdwallet-trezor/src/trezor.ts index 889666766..44ec67057 100644 --- a/packages/hdwallet-trezor/src/trezor.ts +++ b/packages/hdwallet-trezor/src/trezor.ts @@ -10,118 +10,6 @@ export function isTrezor(wallet: core.HDWallet): wallet is TrezorHDWallet { return _.isObject(wallet) && (wallet as any)._isTrezor; } -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) return unknown; - - if (path[3] != 0) return unknown; - - if ((path[4] & 0x80000000) !== 0) return unknown; - - const accountIdx = path[4] & 0x7fffffff; - return { - verbose: `Ethereum Account #${accountIdx}`, - coin: "Ethereum", - accountIdx, - wholeAccount: true, - isKnown: true, - isPrefork: false, - }; -} - -function describeUTXOPath(path: core.BIP32Path, coin: core.Coin, scriptType?: core.BTCInputScriptType) { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin, - scriptType, - isKnown: false, - }; - - if (!Btc.btcSupportsCoin(coin)) return unknown; - - if (!Btc.btcSupportsScriptType(coin, scriptType)) return unknown; - - if (path.length !== 3 && path.length !== 5) return unknown; - - if ((path[0] & 0x80000000) >>> 0 !== 0x80000000) return unknown; - - const purpose = path[0] & 0x7fffffff; - - if (![44, 49, 84].includes(purpose)) return unknown; - - if (purpose === 44 && scriptType !== core.BTCInputScriptType.SpendAddress) return unknown; - - if (purpose === 49 && scriptType !== core.BTCInputScriptType.SpendP2SHWitness) return unknown; - - if (purpose === 84 && scriptType !== core.BTCInputScriptType.SpendWitness) return unknown; - - const slip44 = core.slip44ByCoin(coin); - if (slip44 == undefined || path[1] !== 0x80000000 + slip44) return unknown; - - const wholeAccount = path.length === 3; - - let script = scriptType - ? ( - { - [core.BTCInputScriptType.SpendAddress]: " (Legacy)", - [core.BTCInputScriptType.SpendP2SHWitness]: "", - [core.BTCInputScriptType.SpendWitness]: " (Segwit Native)", - } as Partial> - )[scriptType] ?? "" - : ""; - - switch (coin) { - case "Bitcoin": - case "Litecoin": - case "BitcoinGold": - case "Testnet": - break; - default: - script = ""; - } - - const accountIdx = path[2] & 0x7fffffff; - - if (wholeAccount) { - return { - verbose: `${coin} Account #${accountIdx}${script}`, - scriptType, - coin, - accountIdx, - wholeAccount: true, - isKnown: true, - isPrefork: false, - }; - } else { - const change = path[3] === 1 ? "Change " : ""; - const addressIdx = path[4]; - return { - verbose: `${coin} Account #${accountIdx}, ${change}Address #${addressIdx}${script}`, - coin, - scriptType, - accountIdx, - addressIdx, - isChange: path[3] === 1, - wholeAccount: false, - isKnown: true, - isPrefork: false, - }; - } -} - export class TrezorHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo, core.ETHWalletInfo { readonly _supportsBTCInfo = true; readonly _supportsETHInfo = true; @@ -205,16 +93,11 @@ export class TrezorHDWalletInfo implements core.HDWalletInfo, core.BTCWalletInfo } public describePath(msg: core.DescribePath): core.PathDescription { - switch (msg.coin) { - case "Ethereum": - return describeETHPath(msg.path); - default: - return describeUTXOPath(msg.path, msg.coin, msg.scriptType); - } + return core.describePath(msg, core.ETHAddressDerivationScheme.Metamask); } public btcNextAccountPath(msg: core.BTCAccountPath): core.BTCAccountPath | undefined { - const description = describeUTXOPath(msg.addressNList, msg.coin, msg.scriptType); + const description = core.btcDescribePath(msg.addressNList, msg.coin, msg.scriptType); if (!description.isKnown) { return undefined; } @@ -238,7 +121,7 @@ export class TrezorHDWalletInfo 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.ethDescribePath(addressNList, core.ETHAddressDerivationScheme.Metamask); if (!description.isKnown) { return undefined; } diff --git a/packages/hdwallet-xdefi/src/xdefi.test.ts b/packages/hdwallet-xdefi/src/xdefi.test.ts index c54534f44..34218bcd0 100644 --- a/packages/hdwallet-xdefi/src/xdefi.test.ts +++ b/packages/hdwallet-xdefi/src/xdefi.test.ts @@ -25,8 +25,11 @@ describe("XDeFIHDWalletInfo", () => { msg: { coin: "Ethereum", path: [44 + 0x80000000, 60 + 0x80000000, 0 + 0x80000000, 0, 0] }, out: { coin: "Ethereum", verbose: "Ethereum Account #0", isKnown: true }, }, + { + msg: { coin: "foobar", path: [1, 2, 3] }, + out: { coin: "foobar", verbose: "m/1/2/3", isKnown: false }, + }, ].forEach((x) => expect(info.describePath(x.msg)).toMatchObject(x.out)); - expect(() => info.describePath({ coin: "foobar", path: [1, 2, 3] })).toThrowError("Unsupported path"); }); }); diff --git a/packages/hdwallet-xdefi/src/xdefi.ts b/packages/hdwallet-xdefi/src/xdefi.ts index 2e7b3e239..29b723774 100644 --- a/packages/hdwallet-xdefi/src/xdefi.ts +++ b/packages/hdwallet-xdefi/src/xdefi.ts @@ -43,12 +43,7 @@ export class XDeFiHDWalletInfo implements core.HDWalletInfo, core.ETHWalletInfo } public describePath(msg: core.DescribePath): core.PathDescription { - switch (msg.coin) { - case "Ethereum": - return core.describeETHPath(msg.path); - default: - throw new Error("Unsupported path"); - } + return core.describePath(msg); } // eslint-disable-next-line @typescript-eslint/no-unused-vars