diff --git a/packages/common-types/src/baseTypes/aggregateTypes.ts b/packages/common-types/src/baseTypes/aggregateTypes.ts index 84fb20ac..803bd559 100644 --- a/packages/common-types/src/baseTypes/aggregateTypes.ts +++ b/packages/common-types/src/baseTypes/aggregateTypes.ts @@ -239,6 +239,8 @@ export type LocalTransitionData = [...IAuthMetadatas, ...ShareStores, ...IMessag export type LocalMetadataTransitions = [LocalTransitionShares, LocalTransitionData]; export interface ITKeyApi { + setModuleState(data: T, moduleName: string); + getModuleState(moduleName: string): T; getMetadata(): IMetadata; getStorageLayer(): IStorageLayer; initialize(params: { input?: ShareStore; importKey?: BN; neverInitializeNewKey?: boolean }): Promise; @@ -256,6 +258,7 @@ export interface ITKeyApi { middleware: (generalStore: unknown, oldShareStores: ShareStoreMap, newShareStores: ShareStoreMap) => unknown ): void; _addReconstructKeyMiddleware(moduleName: string, middleware: () => Promise>): void; + _addReconstructKeyMiddlewareV2(moduleName: string, middleware: (tkey: ITKeyApi, moduleName: string) => Promise>): void; _addShareSerializationMiddleware( serialize: (share: BN, type: string) => Promise, deserialize: (serializedShare: unknown, type: string) => Promise @@ -310,3 +313,7 @@ export interface ITKey extends ITKeyApi, ISerializable { getKeyDetails(): KeyDetails; } + +export type ReconstructKeyMiddlewareMapV2 = { + [moduleName: string]: (tkey: ITKeyApi, moduleName: string) => Promise; +}; diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index dec3aa07..c0bc9198 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -30,6 +30,7 @@ import { prettyPrintError, ReconstructedKeyResult, ReconstructKeyMiddlewareMap, + ReconstructKeyMiddlewareMapV2, RefreshMiddlewareMap, RefreshSharesResult, Share, @@ -81,12 +82,16 @@ class ThresholdKey implements ITKey { _shareSerializationMiddleware: ShareSerializationMiddleware; + _reconstructKeyMiddlewareV2: ReconstructKeyMiddlewareMapV2; + storeDeviceShare: (deviceShareStore: ShareStore, customDeviceInfo?: StringifiedType) => Promise; haveWriteMetadataLock: string; serverTimeOffset?: number = 0; + moduleState: Record = {}; + constructor(args?: TKeyArgs) { const { enableLogging = false, modules = {}, serviceProvider, storageLayer, manualSync = false, serverTimeOffset } = args || {}; this.enableLogging = enableLogging; @@ -98,6 +103,7 @@ class ThresholdKey implements ITKey { this.manualSync = manualSync; this._refreshMiddleware = {}; this._reconstructKeyMiddleware = {}; + this._reconstructKeyMiddlewareV2 = {}; this._shareSerializationMiddleware = undefined; this.storeDeviceShare = undefined; this._localMetadataTransitions = [[], []]; @@ -193,6 +199,14 @@ class ThresholdKey implements ITKey { return tb; } + getModuleState(moduleName: string): T { + return this.moduleState[moduleName] as T; + } + + setModuleState(data: T, moduleName: string) { + this.moduleState[moduleName] = data; + } + getStorageLayer(): IStorageLayer { return this.storageLayer; } @@ -439,6 +453,21 @@ class ThresholdKey implements ITKey { }) ); } + + // duplicate for testing purpose only + if (_reconstructKeyMiddleware && Object.keys(this._reconstructKeyMiddlewareV2).length > 0) { + // retireve/reconstruct extra keys that live on metadata + await Promise.all( + Object.keys(this._reconstructKeyMiddlewareV2).map(async (x) => { + if (Object.prototype.hasOwnProperty.call(this._reconstructKeyMiddlewareV2, x)) { + const extraKeys = await this._reconstructKeyMiddlewareV2[x](this, x); + returnObject[x] = extraKeys; + returnObject.allKeys.push(...extraKeys); + } + }) + ); + } + return returnObject; } @@ -1071,6 +1100,10 @@ class ThresholdKey implements ITKey { this._reconstructKeyMiddleware[moduleName] = middleware; } + _addReconstructKeyMiddlewareV2(moduleName: string, middleware: (tkey: ITKeyApi, moduleName: string) => Promise): void { + this._reconstructKeyMiddlewareV2[moduleName] = middleware; + } + _addShareSerializationMiddleware( serialize: (share: BN, type: string) => Promise, deserialize: (serializedShare: unknown, type: string) => Promise @@ -1290,6 +1323,8 @@ class ThresholdKey implements ITKey { getApi(): ITKeyApi { return { + getModuleState: this.getModuleState.bind(this), + setModuleState: this.setModuleState.bind(this), getMetadata: this.getMetadata.bind(this), getStorageLayer: this.getStorageLayer.bind(this), initialize: this.initialize.bind(this), @@ -1297,6 +1332,7 @@ class ThresholdKey implements ITKey { _syncShareMetadata: this._syncShareMetadata.bind(this), _addRefreshMiddleware: this._addRefreshMiddleware.bind(this), _addReconstructKeyMiddleware: this._addReconstructKeyMiddleware.bind(this), + _addReconstructKeyMiddlewareV2: this._addReconstructKeyMiddlewareV2.bind(this), _addShareSerializationMiddleware: this._addShareSerializationMiddleware.bind(this), addShareDescription: this.addShareDescription.bind(this), generateNewShare: this.generateNewShare.bind(this), diff --git a/packages/default/package.json b/packages/default/package.json index 7dad47f7..40319c12 100644 --- a/packages/default/package.json +++ b/packages/default/package.json @@ -47,6 +47,7 @@ "devDependencies": { "@tkey/private-keys": "^7.4.0", "@tkey/seed-phrase": "^7.4.0", + "@tkey/seed-phrase-v2": "^7.4.0", "@toruslabs/eccrypto": "^2.1.1", "@toruslabs/http-helpers": "^3.3.0", "web3-utils": "^1.9.0" diff --git a/packages/default/test/shared.js b/packages/default/test/shared.js index 8b1729d8..480d66a1 100644 --- a/packages/default/test/shared.js +++ b/packages/default/test/shared.js @@ -5,7 +5,8 @@ import { ecCurve, getPubKeyPoint, KEY_NOT_FOUND, SHARE_DELETED } from "@tkey/common-types"; import PrivateKeyModule, { ED25519Format, SECP256K1Format } from "@tkey/private-keys"; import SecurityQuestionsModule from "@tkey/security-questions"; -import SeedPhraseModule, { MetamaskSeedPhraseFormat } from "@tkey/seed-phrase"; +import SeedPhraseModule, { MetamaskSeedPhraseFormat, SEED_PHRASE_MODULE_NAME } from "@tkey/seed-phrase"; +import SeedPhraseModuleV2 from "@tkey/seed-phrase-v2"; import TorusServiceProvider from "@tkey/service-provider-torus"; import ShareTransferModule from "@tkey/share-transfer"; import TorusStorageLayer from "@tkey/storage-layer-torus"; @@ -1052,6 +1053,74 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { tb2.inputShareStore(resp1.deviceShare); const reconstructedKey = await tb2.reconstructKey(); + compareReconstructedKeys(reconstructedKey, { + privKey: resp1.privKey, + seedPhraseModule: [ + new BN("70dc3117300011918e26b02176945cc15c3d548cf49fd8418d97f93af699e46", "hex"), + new BN("4d62a55af3496a7b290a12dd5fd5ef3e051d787dbc005fb74536136949602f9e", "hex"), + ], + privateKeyModule: [ + new BN("4bd0041b7654a9b16a7268a5de7982f2422b15635c4fd170c140dc4897624390", "hex"), + new BN("1ea6edde61c750ec02896e9ac7fe9ac0b48a3630594fdf52ad5305470a2635c0", "hex"), + ], + allKeys: [ + resp1.privKey, + new BN("70dc3117300011918e26b02176945cc15c3d548cf49fd8418d97f93af699e46", "hex"), + new BN("4d62a55af3496a7b290a12dd5fd5ef3e051d787dbc005fb74536136949602f9e", "hex"), + new BN("4bd0041b7654a9b16a7268a5de7982f2422b15635c4fd170c140dc4897624390", "hex"), + new BN("1ea6edde61c750ec02896e9ac7fe9ac0b48a3630594fdf52ad5305470a2635c0", "hex"), + ], + }); + + const reconstructedKey2 = await tb2.reconstructKey(false); + compareReconstructedKeys(reconstructedKey2, { + privKey: resp1.privKey, + allKeys: [resp1.privKey], + }); + }); + + it.only(`#should be able to get/set private keys and seed phrase v2, manualSync=${mode}`, async function () { + const resp1 = await tb._initializeNewKey({ initializeModules: true }); + + await tb.modules.seedPhrase.setSeedPhrase("HD Key Tree", "seed sock milk update focus rotate barely fade car face mechanic mercy"); + await tb.modules.seedPhrase.setSeedPhrase("HD Key Tree", "chapter gas cost saddle annual mouse chef unknown edit pen stairs claw"); + + const actualPrivateKeys = [ + new BN("4bd0041b7654a9b16a7268a5de7982f2422b15635c4fd170c140dc4897624390", "hex"), + new BN("1ea6edde61c750ec02896e9ac7fe9ac0b48a3630594fdf52ad5305470a2635c0", "hex"), + ]; + await tb.modules.privateKeyModule.setPrivateKey("secp256k1n", actualPrivateKeys[0]); + await tb.modules.privateKeyModule.setPrivateKey("secp256k1n", actualPrivateKeys[1]); + await tb.syncLocalMetadataTransitions(); + + const metamaskSeedPhraseFormat2 = new MetamaskSeedPhraseFormat("https://mainnet.infura.io/v3/bca735fdbba0408bb09471e86463ae68"); + const tb2 = new ThresholdKey({ + serviceProvider: customSP, + manualSync: mode, + storageLayer: customSL, + modules: { + // seedPhrase: new SeedPhraseModule([metamaskSeedPhraseFormat2]), + privateKeyModule: new PrivateKeyModule([secp256k1Format]), + }, + }); + await tb2.initialize(); + tb2.inputShareStore(resp1.deviceShare); + + const mdata = { + moduleName: SEED_PHRASE_MODULE_NAME, + seedPhraseFormats: [metamaskSeedPhraseFormat2], + }; + SeedPhraseModuleV2.setModuleReferences(tb2, mdata); + + const reconstructedKey = await tb2.reconstructKey(); + + // Example SeedPhrase Module api call + const key = await SeedPhraseModuleV2.getAccounts(tb2); + console.log(key); + const keyAccounts = await SeedPhraseModuleV2.getSeedPhrasesWithAccounts(tb2); + console.log(keyAccounts) + + compareReconstructedKeys(reconstructedKey, { privKey: resp1.privKey, seedPhraseModule: [ diff --git a/packages/seed-phrase-v2/LICENSE b/packages/seed-phrase-v2/LICENSE new file mode 100644 index 00000000..d38f4bb7 --- /dev/null +++ b/packages/seed-phrase-v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 tKey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/seed-phrase-v2/README.md b/packages/seed-phrase-v2/README.md new file mode 100644 index 00000000..52bef6b8 --- /dev/null +++ b/packages/seed-phrase-v2/README.md @@ -0,0 +1,80 @@ +# tKey Seed Phrase Module + +[![npm version](https://img.shields.io/npm/v/@tkey/seed-phrase?label=%22%22)](https://www.npmjs.com/package/@tkey/seed-phrase/v/latest) [![minzip](https://img.shields.io/bundlephobia/minzip/@tkey/seed-phrase?label=%22%22)](https://bundlephobia.com/result?p=@tkey/seed-phrase@latest) + +The tKey Seed Phrase Module helps you add or remove the and password as a share for tkey. This module is the part of the [tKey SDK](https://github.com/tkey/tkey/). + + +## Installation + +```shell +npm install --save @tkey/seed-phrase +``` + +## Initialization + +#### Import the `SeedPhraseModule` class from `@tkey/seed-phrase` + +```javascript +import SeedPhraseModule from "@tkey/seed-phrase"; +``` + +#### Assign the `SeedPhraseModule` class to a variable + +```javascript +const seedPhraseModule = new SeedPhraseModule(); +``` + +### Returns + +The `SeedPhraseModule` class returns an object with the following properties: + +```ts +declare class SeedPhraseModule implements IModule { + moduleName: string; + tbSDK: ITKeyApi; + seedPhraseFormats: ISeedPhraseFormat[]; + constructor(formats: ISeedPhraseFormat[]); + setModuleReferences(tbSDK: ITKeyApi): void; + initialize(): Promise; + setSeedPhrase(seedPhraseType: string, seedPhrase?: string): Promise; + setSeedPhraseStoreItem(partialStore: ISeedPhraseStore): Promise; + CRITICAL_changeSeedPhrase(oldSeedPhrase: string, newSeedPhrase: string): Promise; + getSeedPhrases(): Promise; + getSeedPhrasesWithAccounts(): Promise; + getAccounts(): Promise; +} +``` + +## Usage + +With the `SeedPhraseModule`, you've access to the following functions: + +### Set Seed Phrase + +#### `setSeedPhrase(seedPhraseType: string, seedPhrase?: string)` + +- `seedPhraseType`: The type of seed phrase to set. +- `seedPhrase`: The seed phrase to set. + +### Set Seed Phrase Store Item + +#### `setSeedPhraseStoreItem(partialStore: ISeedPhraseStore)` + +- `partialStore`: The partial store to set. + +### Get Seed Phrase + +#### `getSeedPhrases()` + +#### Return + +- `Promise`: A list of seed phrases. + +### Get Seed Phrase With Accounts + +#### `getSeedPhrasesWithAccounts()` + +#### Return + +- `Promise`: A list of seed phrases with accounts. diff --git a/packages/seed-phrase-v2/package.json b/packages/seed-phrase-v2/package.json new file mode 100644 index 00000000..a823d419 --- /dev/null +++ b/packages/seed-phrase-v2/package.json @@ -0,0 +1,62 @@ +{ + "name": "@tkey/seed-phrase-v2", + "version": "7.4.0", + "description": "TKey Seed Phrase Module", + "author": "Torus Labs", + "homepage": "https://github.com/tkey/tkey#readme", + "license": "MIT", + "main": "dist/seedPhrase.cjs.js", + "module": "dist/seedPhrase.esm.js", + "unpkg": "dist/seedPhrase.umd.min.js", + "jsdelivr": "dist/seedPhrase.umd.min.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist", + "src" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/tkey/tkey.git" + }, + "scripts": { + "test": "cross-env MOCKED=true mocha --config ../../.mocharc.json ", + "coverage": "nyc yarn test", + "coverage-production": "nyc yarn test-production", + "test-development": "cross-env MOCKED=false METADATA=http://localhost:5051 mocha --config ../../.mocharc.json ", + "test-production": "cross-env MOCKED=false METADATA=https://metadata.tor.us mocha --config ../../.mocharc.json ", + "test-debugger": "mocha --config ../../.mocharc.json --inspect-brk", + "dev": "rimraf dist/ && cross-env NODE_ENV=development torus-scripts build", + "build": "rimraf dist/ && cross-env NODE_ENV=production torus-scripts build", + "lint": "eslint --fix 'src/**/*.ts'", + "prepack": "yarn run build", + "pre-commit": "lint-staged" + }, + "bugs": { + "url": "https://github.com/tkey/tkey/issues" + }, + "peerDependencies": { + "@babel/runtime": "7.x" + }, + "dependencies": { + "@tkey/common-types": "^7.4.0", + "bip39": "^3.1.0", + "bn.js": "^5.2.1", + "hdkey": "^2.1.0", + "web3-core": "^1.9.0", + "web3-eth": "^1.9.0", + "web3-utils": "^1.9.0" + }, + "devDependencies": { + "@types/bn.js": "^5.1.1" + }, + "lint-staged": { + "!(*d).ts": [ + "yarn run lint --", + "prettier --write 'src/**/*.ts'" + ] + }, + "gitHead": "9967ce9f795f495f28ef0da1fc50acde31dcc258" +} diff --git a/packages/seed-phrase-v2/src/MetamaskSeedPhraseFormat.ts b/packages/seed-phrase-v2/src/MetamaskSeedPhraseFormat.ts new file mode 100644 index 00000000..c52149a6 --- /dev/null +++ b/packages/seed-phrase-v2/src/MetamaskSeedPhraseFormat.ts @@ -0,0 +1,78 @@ +import { generateAddressFromPublicKey, generateID, ISeedPhraseFormat, ISeedPhraseStore, MetamaskSeedPhraseStore } from "@tkey/common-types"; +import { generateMnemonic, mnemonicToSeed, validateMnemonic } from "bip39"; +import BN from "bn.js"; +import HDKey from "hdkey"; +import { provider } from "web3-core"; +import { fromWei } from "web3-utils"; + +import Web3Eth from "./web3"; + +class MetamaskSeedPhraseFormat implements ISeedPhraseFormat { + type: string; + + hdPathString: string; + + provider: provider; + + root: HDKey; + + constructor(ethProvider: provider) { + this.hdPathString = `m/44'/60'/0'/0`; + this.type = "HD Key Tree"; + this.provider = ethProvider; + } + + validateSeedPhrase(seedPhrase: string): boolean { + const parsedSeedPhrase = (seedPhrase || "").trim().toLowerCase().match(/\w+/gu)?.join(" ") || ""; + const wordCount = parsedSeedPhrase.split(/\s/u).length; + if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) { + return false; + } + return validateMnemonic(seedPhrase); + } + + async deriveKeysFromSeedPhrase(seedPhraseStore: ISeedPhraseStore): Promise { + const mmStore = seedPhraseStore as MetamaskSeedPhraseStore; + const { seedPhrase } = mmStore; + const seed = await mnemonicToSeed(seedPhrase); + const hdkey = HDKey.fromMasterSeed(seed); + const root = hdkey.derive(this.hdPathString); + + const numOfWallets = mmStore.numberOfWallets; + const wallets: BN[] = []; + for (let i = 0; i < numOfWallets; i += 1) { + const child = root.deriveChild(i); + const wallet = new BN(child.privateKey); + wallets.push(wallet); + } + return wallets; + } + + async createSeedPhraseStore(seedPhrase?: string): Promise { + let numberOfWallets = 0; + const finalSeedPhrase = seedPhrase || generateMnemonic(); + let lastBalance: string; + const web3 = new Web3Eth(this.provider); + const hdkey = HDKey.fromMasterSeed(finalSeedPhrase); + const root = hdkey.derive(this.hdPathString); + + // seek out the first zero balance + while (lastBalance !== "0") { + const wallet = root.deriveChild(numberOfWallets); + const address = generateAddressFromPublicKey(wallet.publicKey); + + lastBalance = await web3.getBalance(address); + lastBalance = fromWei(lastBalance); + + numberOfWallets += 1; + } + + return { + id: generateID(), + type: this.type, + seedPhrase: finalSeedPhrase, + numberOfWallets, + }; + } +} +export default MetamaskSeedPhraseFormat; diff --git a/packages/seed-phrase-v2/src/SeedPhrase.ts b/packages/seed-phrase-v2/src/SeedPhrase.ts new file mode 100644 index 00000000..57a7746d --- /dev/null +++ b/packages/seed-phrase-v2/src/SeedPhrase.ts @@ -0,0 +1,102 @@ +import { ISeedPhraseFormat, ISeedPhraseStore, ISeedPhraseStoreWithKeys, ITKeyApi } from "@tkey/common-types"; +import BN from "bn.js"; + +import SeedPhraseError from "./errors"; + +export const SEED_PHRASE_MODULE_NAME = "seedPhraseModule"; + +interface SeedPhraseModuleData { + moduleName: string; + seedPhraseFormats: ISeedPhraseFormat[]; +} + +class SeedPhraseModule { + static setModuleReferences(tkey: ITKeyApi, data: SeedPhraseModuleData, moduleName = SEED_PHRASE_MODULE_NAME) { + tkey.setModuleState(data, moduleName); + tkey._addReconstructKeyMiddlewareV2(moduleName, SeedPhraseModule.getAccounts); + } + + static async setSeedPhrase(tkey: ITKeyApi, seedPhraseType: string, seedPhrase?: string, moduleName = SEED_PHRASE_MODULE_NAME): Promise { + const data = tkey.getModuleState(moduleName); + if (!data) throw new Error(`module "${moduleName}" not initalized`); + + const format = data.seedPhraseFormats.find((el) => el.type === seedPhraseType); + if (!format) { + throw SeedPhraseError.notSupported(); + } + if (seedPhrase && !format.validateSeedPhrase(seedPhrase)) { + throw SeedPhraseError.invalid(`${seedPhraseType}`); + } + const seedPhraseStore = await format.createSeedPhraseStore(seedPhrase); + return tkey._setTKeyStoreItem(moduleName, seedPhraseStore); + } + + static async setSeedPhraseStoreItem(tkey: ITKeyApi, partialStore: ISeedPhraseStore, moduleName = SEED_PHRASE_MODULE_NAME): Promise { + const data = tkey.getModuleState(moduleName); + if (!data) throw new Error(`module "${moduleName}" not initalized`); + + const seedPhraseItem = (await tkey.getTKeyStoreItem(moduleName, partialStore.id)) as ISeedPhraseStore; + const originalItem: ISeedPhraseStore = { id: seedPhraseItem.id, type: seedPhraseItem.type, seedPhrase: seedPhraseItem.seedPhrase }; + // Disallow editing critical fields + const finalItem = { ...partialStore, ...originalItem }; + return tkey._setTKeyStoreItem(moduleName, finalItem); + } + + static async CRITICAL_changeSeedPhrase( + tkey: ITKeyApi, + oldSeedPhrase: string, + newSeedPhrase: string, + moduleName = SEED_PHRASE_MODULE_NAME + ): Promise { + const data = tkey.getModuleState(moduleName); + if (!data) throw new Error(`module "${moduleName}" not initalized`); + const seedPhrases = await SeedPhraseModule.getSeedPhrases(tkey, moduleName); + const itemToChange = seedPhrases.find((x) => x.seedPhrase === oldSeedPhrase); + itemToChange.seedPhrase = newSeedPhrase; + return tkey._setTKeyStoreItem(moduleName, itemToChange); + } + + static async getSeedPhrases(tkey: ITKeyApi, moduleName = SEED_PHRASE_MODULE_NAME): Promise { + const data = tkey.getModuleState(moduleName); + if (!data) throw new Error(`module "${moduleName}" not initalized`); + return tkey.getTKeyStore(moduleName) as Promise; + } + + static async getSeedPhrasesWithAccounts(tkey: ITKeyApi, moduleName = SEED_PHRASE_MODULE_NAME): Promise { + const data = tkey.getModuleState(moduleName); + if (!data) throw new Error(`module "${moduleName}" not initalized`); + try { + // Get seed phrases for all available formats from TKeyStore + const seedPhrases = await this.getSeedPhrases(tkey, moduleName); + return await Promise.all( + seedPhrases.map(async (x) => { + const suitableFormat = data.seedPhraseFormats.find((y) => y.type === x.type); + const keys = await suitableFormat.deriveKeysFromSeedPhrase(x); + return { ...x, keys }; + }) + ); + } catch (err) { + return []; + } + } + + static async getAccounts(tkey: ITKeyApi, moduleName = SEED_PHRASE_MODULE_NAME): Promise { + const data = tkey.getModuleState(moduleName); + if (!data) throw new Error(`module "${moduleName}" not initalized`); + try { + // Get seed phrases for all available formats from TKeyStore + const seedPhrases = await SeedPhraseModule.getSeedPhrases(tkey, moduleName); + const responses = await Promise.all( + seedPhrases.map(async (x) => { + const suitableFormat = data.seedPhraseFormats.find((y) => y.type === x.type); + return suitableFormat.deriveKeysFromSeedPhrase(x); + }) + ); + return responses.flatMap((x) => x); + } catch (err) { + return []; + } + } +} + +export default SeedPhraseModule; diff --git a/packages/seed-phrase-v2/src/errors.ts b/packages/seed-phrase-v2/src/errors.ts new file mode 100644 index 00000000..3fc4ab81 --- /dev/null +++ b/packages/seed-phrase-v2/src/errors.ts @@ -0,0 +1,36 @@ +import { ErrorCodes, ITkeyError, TkeyError } from "@tkey/common-types"; + +class SeedPhraseError extends TkeyError { + protected static messages: ErrorCodes = { + 6000: "Custom", + // Misc + 6010: "Private key type is not supported", + 6011: "validation failed", + 6012: "Seed phrase is invalid for ", + }; + + public constructor(code: number, message?: string) { + // takes care of stack and proto + super(code, message); + // Set name explicitly as minification can mangle class names + Object.defineProperty(this, "name", { value: "SeedPhraseError" }); + } + + public static fromCode(code: number, extraMessage = ""): ITkeyError { + return new SeedPhraseError(code, `${SeedPhraseError.messages[code]}${extraMessage}`); + } + + // Custom methods + public static notSupported(extraMessage = ""): ITkeyError { + return SeedPhraseError.fromCode(6010, extraMessage); + } + + public static validationFailed(extraMessage = ""): ITkeyError { + return SeedPhraseError.fromCode(6011, extraMessage); + } + + public static invalid(extraMessage = ""): ITkeyError { + return SeedPhraseError.fromCode(6012, extraMessage); + } +} +export default SeedPhraseError; diff --git a/packages/seed-phrase-v2/src/index.ts b/packages/seed-phrase-v2/src/index.ts new file mode 100644 index 00000000..18463358 --- /dev/null +++ b/packages/seed-phrase-v2/src/index.ts @@ -0,0 +1,3 @@ +export { default as SeedPhraseError } from "./errors"; +export { default as MetamaskSeedPhraseFormat } from "./MetamaskSeedPhraseFormat"; +export { default, SEED_PHRASE_MODULE_NAME, default as SeedPhraseModule } from "./SeedPhrase"; diff --git a/packages/seed-phrase-v2/src/web3.ts b/packages/seed-phrase-v2/src/web3.ts new file mode 100644 index 00000000..4d1ad84d --- /dev/null +++ b/packages/seed-phrase-v2/src/web3.ts @@ -0,0 +1,7 @@ +import Web3, { Eth } from "web3-eth"; + +// Because web3-eth typings are wrong +declare module "web3-eth" { + export default class Web3Eth extends Eth {} +} +export default Web3; diff --git a/packages/seed-phrase-v2/test/.eslintrc.json b/packages/seed-phrase-v2/test/.eslintrc.json new file mode 100644 index 00000000..955546bd --- /dev/null +++ b/packages/seed-phrase-v2/test/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "rules": { + "prefer-arrow-callback": "off", + "func-names": "off" + } + +} diff --git a/packages/seed-phrase-v2/torus.config.js b/packages/seed-phrase-v2/torus.config.js new file mode 100644 index 00000000..66be7083 --- /dev/null +++ b/packages/seed-phrase-v2/torus.config.js @@ -0,0 +1 @@ +module.exports = require("../../torus.config"); diff --git a/packages/seed-phrase-v2/tsconfig.build.json b/packages/seed-phrase-v2/tsconfig.build.json new file mode 100644 index 00000000..bcd8df0e --- /dev/null +++ b/packages/seed-phrase-v2/tsconfig.build.json @@ -0,0 +1,3 @@ +{ + "include": ["src"] +} diff --git a/packages/seed-phrase-v2/tsconfig.json b/packages/seed-phrase-v2/tsconfig.json new file mode 100644 index 00000000..ef502e89 --- /dev/null +++ b/packages/seed-phrase-v2/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "test"] +} diff --git a/packages/seed-phrase-v2/webpack.config.js b/packages/seed-phrase-v2/webpack.config.js new file mode 100644 index 00000000..a389ab71 --- /dev/null +++ b/packages/seed-phrase-v2/webpack.config.js @@ -0,0 +1,11 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const path = require("path"); +const generateWebpackConfig = require("../../webpack.config"); + +const pkg = require("./package.json"); + +const currentPath = path.resolve("."); + +const config = generateWebpackConfig({ currentPath, pkg }); + +exports.baseConfig = config.baseConfig;