@funded-labs/plug-controller
Version:
Internet Computer Plug wallet's controller
433 lines (432 loc) • 22.1 kB
JavaScript
"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;