@colony/purser-software
Version:
A javascript library to interact with a software Ethereum wallet, based on the ethers.js library
491 lines (432 loc) • 17.1 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _secretStorage = require("ethers/utils/secret-storage");
var _ethereumjsUtil = require("ethereumjs-util");
var _helpers = require("@colony/purser-core/helpers");
var _utils = require("@colony/purser-core/utils");
var _normalizers = require("@colony/purser-core/normalizers");
var _validators = require("@colony/purser-core/validators");
var _defaults = require("@colony/purser-core/defaults");
var _types = require("@colony/purser-core/types");
var _staticMethods = require("./staticMethods");
var _messages = require("./messages");
var GETTERS = _defaults.DESCRIPTORS.GETTERS,
WALLET_PROPS = _defaults.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] : {};
(0, _classCallCheck2.default)(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.
*/
(0, _validators.addressValidator)(address);
(0, _validators.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: _types.TYPE_SOFTWARE
}, WALLET_PROPS),
subtype: Object.assign({}, {
value: _types.SUBTYPE_ETHERS
}, WALLET_PROPS),
chainId: Object.assign({}, {
value: chainId
}, WALLET_PROPS),
/*
* Getters
*/
privateKey: Object.assign({}, {
get: function () {
var _get = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee() {
return _regenerator.default.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 = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee2() {
return _regenerator.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
return _context2.abrupt("return", (0, _helpers.derivationPathSerializer)({
change: _defaults.PATH.CHANGE,
addressIndex: _defaults.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 = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee3(transactionObject) {
var _ref, _ref$chainId, transactionChainId;
return _regenerator.default.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
/*
* Validate the trasaction's object input
*/
(0, _helpers.userInputValidator)({
firstArgument: transactionObject
});
_ref = transactionObject || {}, _ref$chainId = _ref.chainId, transactionChainId = _ref$chainId === void 0 ? _this.chainId : _ref$chainId;
return _context3.abrupt("return", (0, _staticMethods.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 = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee4() {
var messageObject,
_args4 = arguments;
return _regenerator.default.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
*/
(0, _helpers.userInputValidator)({
firstArgument: messageObject,
requiredOr: _defaults.REQUIRED_PROPS.SIGN_MESSAGE
});
return _context4.abrupt("return", (0, _staticMethods.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 = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee5() {
var signatureVerificationObject,
message,
signature,
_args5 = arguments;
return _regenerator.default.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
*/
(0, _helpers.userInputValidator)({
firstArgument: signatureVerificationObject,
requiredAll: _defaults.REQUIRED_PROPS.VERIFY_MESSAGE
});
message = signatureVerificationObject.message, signature = signatureVerificationObject.signature;
return _context5.abrupt("return", (0, _staticMethods.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 = (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee6() {
return _regenerator.default.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));
}
}
(0, _createClass2.default)(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 (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee7() {
var privateKey;
return _regenerator.default.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
*/
(0, _secretStorage.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
*/
(0, _secretStorage.encrypt)(privateKey, internalEncryptionPassword.toString()));
case 6:
(0, _utils.warning)(_messages.walletClass.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 (0, _asyncToGenerator2.default)(
/*#__PURE__*/
_regenerator.default.mark(function _callee8() {
var privateKey, reversedPublicKey, normalizedPublicKey;
return _regenerator.default.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 = (0, _ethereumjsUtil.privateToPublic)(privateKey).toString(_defaults.HEX_HASH_TYPE);
/*
* Validate the reversed public key
*/
(0, _validators.hexSequenceValidator)(reversedPublicKey);
/*
* Then normalize it to ensure it has the `0x` prefix
*/
normalizedPublicKey = (0, _normalizers.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
*/
exports.default = SoftwareWallet;
Object.defineProperties(SoftwareWallet.prototype, {
publicKey: GETTERS,
keystore: GETTERS
});