UNPKG

@colony/purser-software

Version:

A javascript library to interact with a software Ethereum wallet, based on the ethers.js library

471 lines (426 loc) 16.5 kB
import _regeneratorRuntime from "@babel/runtime/regenerator"; import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator"; import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; import _createClass from "@babel/runtime/helpers/esm/createClass"; import { encrypt } from 'ethers/utils/secret-storage'; import { privateToPublic } from 'ethereumjs-util'; import { derivationPathSerializer, userInputValidator } from '@colony/purser-core/helpers'; import { warning } from '@colony/purser-core/utils'; import { hexSequenceNormalizer } from '@colony/purser-core/normalizers'; import { addressValidator, hexSequenceValidator } from '@colony/purser-core/validators'; import { PATH, DESCRIPTORS, HEX_HASH_TYPE, REQUIRED_PROPS } from '@colony/purser-core/defaults'; import { TYPE_SOFTWARE, SUBTYPE_ETHERS } from '@colony/purser-core/types'; import { signTransaction, signMessage, verifyMessage } from './staticMethods'; import { walletClass as messages } from './messages'; var GETTERS = DESCRIPTORS.GETTERS, WALLET_PROPS = DESCRIPTORS.WALLET_PROPS; /* * "Private" (internal) variable(s) */ var internalKeystoreJson; var internalEncryptionPassword; /** * @NOTE We're no longer directly extending the Ethers Wallet Class * * This is due to the fact that we need more control over the resulting Class * object (SoftwareWallet in this case). * * We're still shadowing the Ethers Wallet, meaning when opening or creating a new * wallet, we will first create a Ethers Wallet instance than pass that along * to the SoftwareWallet constructor. * * This way we don't have to deal with non-configurable or non-writable props, * or the providers being baked in. */ var SoftwareWallet = /*#__PURE__*/ function () { /* * @TODO Add specific Flow types * * For the three main wallet methods */ function SoftwareWallet() { var _this = this; var ethersInstance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _classCallCheck(this, SoftwareWallet); var address = ethersInstance.address, privateKey = ethersInstance.privateKey, password = ethersInstance.password, mnemonic = ethersInstance.originalMnemonic, keystore = ethersInstance.keystore, chainId = ethersInstance.chainId, ethersSign = ethersInstance.sign, ethersSignMessage = ethersInstance.signMessage; /* * Validate the private key and address that's coming in from ethers. */ addressValidator(address); hexSequenceValidator(privateKey); /* * If we have a keystore JSON string and encryption password, set them * to the internal variables. */ internalEncryptionPassword = password; internalKeystoreJson = keystore; /* * Set the private key to a "internal" variable since we only allow * access to it through a getter and not directly via a prop. */ Object.defineProperties(this, { address: Object.assign({}, { value: address }, WALLET_PROPS), type: Object.assign({}, { value: TYPE_SOFTWARE }, WALLET_PROPS), subtype: Object.assign({}, { value: SUBTYPE_ETHERS }, WALLET_PROPS), chainId: Object.assign({}, { value: chainId }, WALLET_PROPS), /* * Getters */ privateKey: Object.assign({}, { get: function () { var _get = _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee() { return _regeneratorRuntime.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: return _context.abrupt("return", privateKey); case 1: case "end": return _context.stop(); } } }, _callee, this); })); return function get() { return _get.apply(this, arguments); }; }() }, GETTERS), /* * @TODO Allow users control of the derivation path * When instantiating a new class instance. But this is only if the feature * turns out to be required. */ derivationPath: Object.assign({}, { get: function () { var _get2 = _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee2() { return _regeneratorRuntime.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: return _context2.abrupt("return", derivationPathSerializer({ change: PATH.CHANGE, addressIndex: PATH.INDEX })); case 1: case "end": return _context2.stop(); } } }, _callee2, this); })); return function get() { return _get2.apply(this, arguments); }; }() }, GETTERS), sign: Object.assign({}, { value: function () { var _value = _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee3(transactionObject) { var _ref, _ref$chainId, transactionChainId; return _regeneratorRuntime.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: /* * Validate the trasaction's object input */ userInputValidator({ firstArgument: transactionObject }); _ref = transactionObject || {}, _ref$chainId = _ref.chainId, transactionChainId = _ref$chainId === void 0 ? _this.chainId : _ref$chainId; return _context3.abrupt("return", signTransaction(Object.assign({}, transactionObject, { chainId: transactionChainId, /* * @NOTE We need to bind the whole ethers instance * * Since the `sign` will look for different methods inside the * class's prototype, and if it fails to find them, it will * crash */ callback: ethersSign.bind(ethersInstance) }))); case 3: case "end": return _context3.stop(); } } }, _callee3, this); })); return function value(_x) { return _value.apply(this, arguments); }; }() }, WALLET_PROPS), signMessage: Object.assign({}, { value: function () { var _value2 = _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee4() { var messageObject, _args4 = arguments; return _regeneratorRuntime.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: messageObject = _args4.length > 0 && _args4[0] !== undefined ? _args4[0] : {}; /* * Validate the trasaction's object input */ userInputValidator({ firstArgument: messageObject, requiredOr: REQUIRED_PROPS.SIGN_MESSAGE }); return _context4.abrupt("return", signMessage({ message: messageObject.message, messageData: messageObject.messageData, /* * @NOTE We need to bind the whole ethers instance * * Since the `signMessage` will look for different methods inside the * class's prototype, and if it fails to find them, it will * crash */ callback: ethersSignMessage.bind(ethersInstance) })); case 3: case "end": return _context4.stop(); } } }, _callee4, this); })); return function value() { return _value2.apply(this, arguments); }; }() }, WALLET_PROPS), verifyMessage: Object.assign({}, { value: function () { var _value3 = _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee5() { var signatureVerificationObject, message, signature, _args5 = arguments; return _regeneratorRuntime.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: signatureVerificationObject = _args5.length > 0 && _args5[0] !== undefined ? _args5[0] : {}; /* * Validate the trasaction's object input */ userInputValidator({ firstArgument: signatureVerificationObject, requiredAll: REQUIRED_PROPS.VERIFY_MESSAGE }); message = signatureVerificationObject.message, signature = signatureVerificationObject.signature; return _context5.abrupt("return", verifyMessage({ address: address, message: message, signature: signature })); case 4: case "end": return _context5.stop(); } } }, _callee5, this); })); return function value() { return _value3.apply(this, arguments); }; }() }, WALLET_PROPS) }); /* * Only set the `mnemonic` prop if it's available, so it won't show up * as being defined, but set to `undefined` */ if (mnemonic) { Object.defineProperty(this, 'mnemonic', Object.assign({}, { get: function () { var _get3 = _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee6() { return _regeneratorRuntime.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: return _context6.abrupt("return", mnemonic); case 1: case "end": return _context6.stop(); } } }, _callee6, this); })); return function get() { return _get3.apply(this, arguments); }; }() }, GETTERS)); } } _createClass(SoftwareWallet, [{ key: "keystore", get: function get() { var _this2 = this; /* * We're wrapping the getter (returning actually) in a IIFE so we can * write it using a `async` pattern. */ return _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee7() { var privateKey; return _regeneratorRuntime.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: if (!internalEncryptionPassword) { _context7.next = 6; break; } _context7.next = 3; return _this2.privateKey; case 3: privateKey = _context7.sent; /* * Memoizing the getter * * This is quite an expensive operation, so we're memoizing it that * on the next call (an the others after that) it won't re-calculate * the value again. */ Object.defineProperty(_this2, 'keystore', Object.assign({}, GETTERS, { value: internalKeystoreJson && Promise.resolve(internalKeystoreJson) || /* * We're usign Ethers's direct secret storage encrypt method to generate * the keystore JSON string * * @TODO Validate the password * * The password won't work if it's not a string, so it will be best if * we write a string validator for it */ encrypt(privateKey, internalEncryptionPassword.toString()) })); return _context7.abrupt("return", internalKeystoreJson && Promise.resolve(internalKeystoreJson) || /* * We're usign Ethers's direct secret storage encrypt method to generate * the keystore JSON string * * @TODO Validate the password * * The password won't work if it's not a string, so it will be best if * we write a string validator for it */ encrypt(privateKey, internalEncryptionPassword.toString())); case 6: warning(messages.noPassword); return _context7.abrupt("return", Promise.reject()); case 8: case "end": return _context7.stop(); } } }, _callee7, this); }))(); } /* * Just set the encryption password, we don't return anything from here, * hence we don't have a need for `this`. * * This is just an convenince to allow us to set the encryption password * after the wallet has be created / instantiated. */ /* eslint-disable-next-line class-methods-use-this */ , set: function set(newEncryptionPassword) { internalEncryptionPassword = newEncryptionPassword; } }, { key: "publicKey", get: function get() { var _this3 = this; /* * We're wrapping the getter (returning actually) in a IIFE so we can * write it using a `async` pattern. */ return _asyncToGenerator( /*#__PURE__*/ _regeneratorRuntime.mark(function _callee8() { var privateKey, reversedPublicKey, normalizedPublicKey; return _regeneratorRuntime.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: _context8.next = 2; return _this3.privateKey; case 2: privateKey = _context8.sent; reversedPublicKey = privateToPublic(privateKey).toString(HEX_HASH_TYPE); /* * Validate the reversed public key */ hexSequenceValidator(reversedPublicKey); /* * Then normalize it to ensure it has the `0x` prefix */ normalizedPublicKey = hexSequenceNormalizer(reversedPublicKey); /* * Memoizing the getter * * While this is not an expensive operation, it's still a good idea * to memoize it so it returns a tiny bit faster. */ Object.defineProperty(_this3, 'publicKey', Object.assign({}, GETTERS, { value: Promise.resolve(normalizedPublicKey) })); return _context8.abrupt("return", normalizedPublicKey); case 8: case "end": return _context8.stop(); } } }, _callee8, this); }))(); } }]); return SoftwareWallet; }(); /* * We need to use `defineProperties` to make props enumerable. * When adding them via a `Class` getter/setter it will prevent that by default */ export { SoftwareWallet as default }; Object.defineProperties(SoftwareWallet.prototype, { publicKey: GETTERS, keystore: GETTERS });