UNPKG

@funded-labs/plug-controller

Version:

Internet Computer Plug wallet's controller

433 lines (432 loc) 22.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto_js_1 = __importDefault(require("crypto-js")); const json_bigint_1 = __importDefault(require("json-bigint")); const uuid_1 = require("uuid"); const PlugWallet_1 = __importDefault(require("../PlugWallet")); const errors_1 = require("../errors"); const identityFactory_1 = require("./../utils/identity/identityFactory"); const utils_1 = require("../utils/storage/utils"); const account_1 = require("../utils/account"); const object_1 = require("../utils/object"); const constants_1 = require("../utils/account/constants"); const account_2 = require("../utils/account"); const version_1 = require("../utils/version"); const storage_1 = __importDefault(require("../utils/storage")); const NetworkModule_1 = __importDefault(require("./modules/NetworkModule")); const constants_2 = require("./constants"); const parsePem_1 = require("./../utils/identity/parsePem"); const identity_1 = __importDefault(require("../utils/identity/secpk256k1/identity")); const arrayBuffer_1 = require("../utils/arrayBuffer"); class PlugKeyRing { constructor(StorageAdapter = new storage_1.default(), CryptoAdapter = crypto_js_1.default, FetchAdapter, blsVerify) { this.isUnlocked = false; this.isInitialized = false; this.getPublicKey = (subaccount) => __awaiter(this, void 0, void 0, function* () { const wallet = yield this.getWallet(subaccount); return wallet.publicKey; }); // Keyring aux methods this.getWallet = (subaccount) => __awaiter(this, void 0, void 0, function* () { var _a; yield this.checkInitialized(); this.checkUnlocked(); const uuid = subaccount !== null && subaccount !== void 0 ? subaccount : this.currentWalletId; this.validateSubaccount(uuid); return (_a = this.state) === null || _a === void 0 ? void 0 : _a.wallets[uuid]; }); this.updateWallet = (wallet) => __awaiter(this, void 0, void 0, function* () { yield this.checkUnlocked(); const wallets = this.state.wallets; wallets[wallet.walletId] = wallet; this.state.wallets = wallets; yield this.saveEncryptedState({ wallets }, this.state.password); }); this.getWalletIdFromIndex = (index) => __awaiter(this, void 0, void 0, function* () { if (index < 0 || !Number.isInteger(index) || !this.state.walletIds || index >= (this.state.walletIds.length || 0)) { throw new Error(errors_1.ERRORS.INVALID_WALLET_NUMBER); } return this.state.walletIds[index]; }); this.init = () => __awaiter(this, void 0, void 0, function* () { const state = (yield this.storage.get()); this.isUnlocked = !!(state === null || state === void 0 ? void 0 : state.isUnlocked); this.isInitialized = !!(state === null || state === void 0 ? void 0 : state.isInitialized); this.currentWalletId = (state === null || state === void 0 ? void 0 : state.currentWalletId) || this.currentWalletId; }); // Storage get this.loadFromPersistance = (password) => __awaiter(this, void 0, void 0, function* () { const storage = ((yield this.storage.get()) || {}); const { vault, isInitialized, currentWalletId, version, networkModule, } = storage; const networkModuleBis = networkModule; if (isInitialized && vault) { const newVersion = (0, version_1.getVersion)(); const _decrypted = newVersion !== version ? (0, utils_1.handleStorageUpdate)(version, Object.assign(Object.assign({}, this.decryptState(vault, password)), { networkModuleBis })) : this.decryptState(vault, password); const { mnemonic, mnemonicWalletCount } = _decrypted, decrypted = __rest(_decrypted, ["mnemonic", "mnemonicWalletCount"]); this.networkModule = new NetworkModule_1.default(Object.assign(Object.assign({}, (newVersion !== version ? _decrypted.networkModule || {} : networkModule)), { fetch: this.fetch, storage: this.storage, onNetworkChange: this.exposeWalletMethods.bind(this) })); const walletsArray = Object.values(_decrypted.wallets); const wallets = walletsArray.reduce((walletsAccum, wallet) => (Object.assign(Object.assign({}, walletsAccum), { [wallet.walletId]: new PlugWallet_1.default(Object.assign(Object.assign({}, wallet), { fetch: this.fetch, network: this.networkModule.network, identity: identityFactory_1.IdentityFactory.createIdentity(wallet.type, wallet.keyPair), blsVerify: this.blsVerify })) })), {}); this.state = Object.assign(Object.assign({}, decrypted), { wallets, mnemonicWalletCount }); this.isInitialized = isInitialized; this.currentWalletId = newVersion !== version ? decrypted.currentWalletId || this.currentWalletId : currentWalletId; this.exposeWalletMethods(); if (newVersion !== version) { yield this.saveEncryptedState({ wallets, mnemonicWalletCount }, password, mnemonic); yield this.storage.set({ version: newVersion, currentWalletId: this.currentWalletId, }); } } }); // Key Management this.create = ({ password = '', icon, name, entropy, }) => __awaiter(this, void 0, void 0, function* () { const { mnemonic } = (0, account_2.createAccount)(entropy); const wallet = yield this.createAndPersistKeyRing({ mnemonic, password, icon, name, }); return { wallet, mnemonic }; }); // Key Management this.importMnemonic = ({ mnemonic, password, }) => __awaiter(this, void 0, void 0, function* () { const wallet = yield this.createAndPersistKeyRing({ mnemonic, password }); return { wallet, mnemonic }; }); this.importAccountFromPem = ({ icon, name, pem, }) => __awaiter(this, void 0, void 0, function* () { yield this.checkInitialized(); this.checkUnlocked(); const walletId = (0, uuid_1.v4)(); const orderNumber = Object.keys(this.state.wallets).length; const { identity, type } = (0, parsePem_1.getIdentityFromPem)(pem); const wallet = new PlugWallet_1.default({ icon, name, walletId, orderNumber, fetch: this.fetch, network: this.networkModule.network, type, identity, blsVerify: this.blsVerify, }); if (this.checkRepeatedAccount(wallet.principal)) { throw new Error(errors_1.ERRORS.INVALID_ACCOUNT); } const wallets = Object.assign(Object.assign({}, this.state.wallets), { [walletId]: wallet }); this.state.wallets = wallets; yield this.saveEncryptedState({ wallets }, this.state.password); return wallet; }); this.importAccountFromPrivateKey = ({ icon, name, secretKey, }) => __awaiter(this, void 0, void 0, function* () { yield this.checkInitialized(); this.checkUnlocked(); const walletId = (0, uuid_1.v4)(); const orderNumber = Object.keys(this.state.wallets).length; const buffSecretKey = (0, arrayBuffer_1.bufferFromHex)(secretKey); const identity = identity_1.default.fromSecretKey(buffSecretKey); const wallet = new PlugWallet_1.default({ icon, name, walletId, orderNumber, fetch: this.fetch, network: this.networkModule.network, type: constants_1.Types.secretKey256k1, identity, blsVerify: this.blsVerify, }); if (this.checkRepeatedAccount(wallet.principal)) { throw new Error(errors_1.ERRORS.INVALID_ACCOUNT); } const wallets = Object.assign(Object.assign({}, this.state.wallets), { [walletId]: wallet }); this.state.wallets = wallets; yield this.saveEncryptedState({ wallets }, this.state.password); return wallet; }); this.getPrincipalFromPem = ({ pem, }) => __awaiter(this, void 0, void 0, function* () { yield this.checkInitialized(); this.checkUnlocked(); const { identity } = (0, parsePem_1.getIdentityFromPem)(pem); const principal = identity.getPrincipal().toText(); return principal; }); this.deleteImportedAccount = (walletId) => __awaiter(this, void 0, void 0, function* () { yield this.checkInitialized(); this.checkUnlocked(); const wallets = this.state.wallets; if (wallets[walletId] && wallets[walletId].type == constants_1.Types.mnemonic) { throw new Error(errors_1.ERRORS.DELETE_ACCOUNT_ERROR); } const _b = wallets, _c = walletId, deletedWallet = _b[_c], maintainedWallets = __rest(_b, [typeof _c === "symbol" ? _c : _c + ""]); if (walletId == this.currentWalletId) { const currentWalletId = this.getMainAccountId(); this.currentWalletId = currentWalletId; yield this.storage.set({ currentWalletId }); } yield this.saveEncryptedState({ wallets: maintainedWallets }, this.state.password); this.state.wallets = maintainedWallets; }); this.validatePem = ({ pem, }) => __awaiter(this, void 0, void 0, function* () { try { const { identity } = (0, parsePem_1.getIdentityFromPem)(pem); const principal = identity === null || identity === void 0 ? void 0 : identity.getPrincipal().toText(); if (this.checkRepeatedAccount(principal)) { return { isValid: false, errorType: errors_1.ERROR_CODES.ADDED_ACCOUNT }; } return { isValid: true }; } catch (_d) { return { isValid: false, errorType: errors_1.ERROR_CODES.INVALID_KEY }; } }); // Key Management this.createPrincipal = (opts) => __awaiter(this, void 0, void 0, function* () { yield this.checkInitialized(); this.checkUnlocked(); const mnemonic = yield this.getMnemonic(this.state.password); const walletId = (0, uuid_1.v4)(); const walletNumber = this.state.mnemonicWalletCount; const orderNumber = Object.keys(this.state.wallets).length; const { identity } = (0, account_1.createAccountFromMnemonic)(mnemonic, walletNumber); const wallet = new PlugWallet_1.default(Object.assign(Object.assign({}, opts), { walletId, orderNumber, fetch: this.fetch, network: this.networkModule.network, type: constants_1.Types.mnemonic, identity, blsVerify: this.blsVerify })); const wallets = Object.assign(Object.assign({}, this.state.wallets), { [walletId]: wallet }); yield this.saveEncryptedState({ wallets, mnemonicWalletCount: walletNumber + 1 }, this.state.password); this.state.wallets = wallets; this.state.mnemonicWalletCount = walletNumber + 1; return wallet; }); // Key Management this.setCurrentPrincipal = (walletId) => __awaiter(this, void 0, void 0, function* () { yield this.checkInitialized(); this.validateSubaccount(walletId); this.currentWalletId = walletId; yield this.storage.set({ currentWalletId: walletId }); }); // General this.getState = () => __awaiter(this, void 0, void 0, function* () { yield this.checkInitialized(); this.checkUnlocked(); return (0, object_1.recursiveParseBigint)(Object.assign(Object.assign({}, this.state), { currentWalletId: this.currentWalletId })); }); // General this.unlock = (password) => __awaiter(this, void 0, void 0, function* () { var _e; yield this.checkInitialized(); try { yield this.loadFromPersistance(password); this.isUnlocked = password === ((_e = this.state) === null || _e === void 0 ? void 0 : _e.password); yield this.storage.set({ isUnlocked: this.isUnlocked }); return this.isUnlocked; } catch (e) { console.error('UNLOCK ERROR:', e); this.isUnlocked = false; return false; } }); // General this.lock = () => __awaiter(this, void 0, void 0, function* () { this.isUnlocked = false; this.state = { wallets: {}, mnemonicWalletCount: 0 }; yield this.storage.set({ isUnlocked: this.isUnlocked }); }); // Key Management this.editPrincipal = (walletId, { name, emoji }) => __awaiter(this, void 0, void 0, function* () { const wallet = yield this.getWallet(walletId); if (name) wallet.setName(name); if (emoji) wallet.setIcon(emoji); yield this.updateWallet(wallet); }); this.checkInitialized = () => __awaiter(this, void 0, void 0, function* () { yield this.init(); if (!this.isInitialized) throw new Error(errors_1.ERRORS.NOT_INITIALIZED); }); this.getPemFile = (walletId) => __awaiter(this, void 0, void 0, function* () { const wallet = yield this.getWallet(walletId); return wallet.pemFile; }); this.getIdentity = (walletId) => __awaiter(this, void 0, void 0, function* () { const wallet = yield this.getWallet(walletId); return wallet.getIdentity(); }); this.checkUnlocked = () => { if (!this.isUnlocked) { throw new Error(errors_1.ERRORS.STATE_LOCKED); } }; // General this.createAndPersistKeyRing = ({ mnemonic, password, icon, name, }) => __awaiter(this, void 0, void 0, function* () { if (!password) throw new Error(errors_1.ERRORS.PASSWORD_REQUIRED); const walletId = this.currentWalletId; const { identity } = (0, account_1.createAccountFromMnemonic)(mnemonic, 0); const wallet = new PlugWallet_1.default({ icon, name, walletId, orderNumber: 0, fetch: this.fetch, network: this.networkModule.network, identity: identity, type: constants_1.Types.mnemonic, blsVerify: this.blsVerify, }); const data = { wallets: { [walletId]: wallet.toJSON() }, password, mnemonic, mnemonicWalletCount: 1, }; this.isInitialized = true; this.currentWalletId = walletId; this.state.mnemonicWalletCount = 1; yield this.storage.clear(); yield this.storage.set({ isInitialized: true, isUnlocked: true, currentWalletId: walletId, version: (0, version_1.getVersion)(), vault: this.crypto.AES.encrypt(JSON.stringify({ mnemonic }), password).toString(), // Pre-save mnemonic in storage }); yield this.saveEncryptedState(data, password); yield this.unlock(password); return wallet; }); // Storage this.saveEncryptedState = (newState, password, defaultMnemonic) => __awaiter(this, void 0, void 0, function* () { const mnemonic = defaultMnemonic || (yield this.getMnemonic(password)); const stringData = json_bigint_1.default.stringify(Object.assign(Object.assign(Object.assign({}, this.state), newState), { mnemonic })); const encrypted = this.crypto.AES.encrypt(stringData, password); yield this.storage.set({ vault: encrypted.toString() }); }); // Storage this.decryptState = (state, password) => JSON.parse(this.crypto.AES.decrypt(state, password).toString(this.crypto.enc.Utf8)); this.checkPassword = (password) => __awaiter(this, void 0, void 0, function* () { yield this.checkInitialized(); try { const { vault, isInitialized } = ((yield this.storage.get()) || {}); if (isInitialized && vault) { const decrypted = this.decryptState(vault, password); return decrypted.password === password; } return false; } catch (e) { return false; } }); // Utils this.getMainAccountId = () => { const { wallets } = this.state; const mainAccount = Object.values(wallets).find(wallet => wallet.orderNumber === 0); return (mainAccount === null || mainAccount === void 0 ? void 0 : mainAccount.walletId) || this.currentWalletId; }; this.state = { wallets: {}, mnemonicWalletCount: 0 }; this.isUnlocked = false; this.isInitialized = false; this.currentWalletId = (0, uuid_1.v4)(); this.storage = StorageAdapter; this.crypto = CryptoAdapter; this.fetch = FetchAdapter; this.networkModule = new NetworkModule_1.default({ fetch: this.fetch, storage: StorageAdapter, onNetworkChange: this.exposeWalletMethods.bind(this), blsVerify: blsVerify, }); this.exposeWalletMethods(); this.exposeMainWalletMethods(); this.blsVerify = blsVerify; } // Wallet proxy methods exposeWalletMethods() { constants_2.WALLET_METHODS.forEach(method => { this[method] = (args) => __awaiter(this, void 0, void 0, function* () { var _a; const _b = args || {}, { subaccount } = _b, params = __rest(_b, ["subaccount"]); const wallet = yield this.getWallet(subaccount); yield wallet.setNetwork((_a = this.networkModule) === null || _a === void 0 ? void 0 : _a.network); const response = yield wallet[method](params); yield this.updateWallet(wallet); return response; }); }); } exposeMainWalletMethods() { constants_2.MAIN_WALLET_METHODS.forEach(method => { this[method] = (args) => __awaiter(this, void 0, void 0, function* () { var _a; const params = __rest(args || {}, []); const mainAccountId = this.getMainAccountId(); const wallet = yield this.getWallet(mainAccountId); yield wallet.setNetwork((_a = this.networkModule) === null || _a === void 0 ? void 0 : _a.network); const response = yield wallet[method](params); yield this.updateWallet(wallet); return response; }); }); } getMnemonic(password) { return __awaiter(this, void 0, void 0, function* () { const storage = (yield this.storage.get()); const decrypted = yield this.decryptState(storage === null || storage === void 0 ? void 0 : storage.vault, password); return decrypted.mnemonic || ''; }); } // This should only be used in import, not in derivation // to avoid throwing when deriving an account that had been previously imported checkRepeatedAccount(principal) { const wallets = Object.values(this.state.wallets); if (wallets.find(wallet => wallet.principal == principal)) { return true; } return false; } validateSubaccount(subaccount) { if (!this.state.wallets[subaccount]) { throw new Error(errors_1.ERRORS.INVALID_WALLET_NUMBER); } } } exports.default = PlugKeyRing;