@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
JavaScript
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
});