UNPKG

react-native-eth-typed-data

Version:

A library to simplifiy interacting with and signing EIP712 typed data

387 lines (321 loc) 16.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = Type; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _ethers = require("ethers"); var _buffer = require("buffer"); var _AbstractType2 = _interopRequireDefault(require("./AbstractType")); var _verify = require("./verify"); var _primitives = require("./primitives"); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } /** * A factory function which returns a class representing an EIP712 Type * The returned Type can be instantiated and validated as an instance * of a particular type * * There are two acceptable formats for the type definitions, a list of * { * name1: 'string', * name2: 'string', * } * * or * * [{ * name: 'name1', * type: 'string' * }, { * name: 'name2', * type: 'string' * }] * * @this {Domain} The domain object to which the returned type should be associated * * @param {String} name A String to define the type * @param {Object|Object[]} defs The definition of the type's members and their types * @returns {Function} the constructor for the new StructureType */ function Type(primaryType, defs) { // Ensure that domain is defined var domain = this || { types: {} }; if (!domain.types) domain.types = {}; // Process the type definition into an array of {name, type} pairs, // keeping track of all dependent types var properties = Array.isArray(defs) ? defs.map(function (_ref) { var name = _ref.name, type = _ref.type; return validateTypeDefinition({ name: name, type: type }, domain); }) : Object.keys(defs).map(function (name) { return validateTypeDefinition({ name: name, type: defs[name] }, domain); }); // Descend through the properties list to come up with a list of // structure types that this type includes, sorted alphabetically var dependencies = findDependencies(properties, domain, []).sort(); /** * @classdesc * This is the dynamically created class that represents a particular Struct type in * the EIP712 scheme. This can be instantiated with particular values that match the * type's definition, and provides methods to encode the type in various formats, and * sign it with a provided signer. */ var StructureType = /*#__PURE__*/function (_AbstractType) { (0, _inherits2["default"])(StructureType, _AbstractType); var _super = _createSuper(StructureType); function StructureType(vals) { var _this; (0, _classCallCheck2["default"])(this, StructureType); _this = _super.call(this); _this.name = primaryType; _this.properties = properties; // Save values for a type instance privately _this._object = {}; var _iterator = _createForOfIteratorHelper(properties), _step; try { var _loop = function _loop() { var prop = _step.value; if (!(prop.name in vals)) { throw new Error("Type ".concat(_this.name, " missing required property ").concat(prop.name)); } // Expose getters and setters for this.prop, validating on set Object.defineProperty((0, _assertThisInitialized2["default"])(_this), prop.name, { get: function get() { return _this._object[prop.name]; }, set: function set(val) { return _this._object[prop.name] = domain.validate(prop.type, val); } }); _this._object[prop.name] = domain.validate(prop.type, vals[prop.name]); }; for (_iterator.s(); !(_step = _iterator.n()).done;) { _loop(); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return _this; } /** * @override @static * Return the part of the type encoding that consists of the dependent types * i.e. the nested Structure types contained by this type. * @returns {String} A string encoding all types upon which this type depends */ (0, _createClass2["default"])(StructureType, [{ key: "toObject", /** * @override * Return a bare object representation of this instance (as a new object) * * @returns {Object} new object containing same key-value pairs of this instance */ value: function toObject() { var _this2 = this; // Generate a new bare object, with each complex item decomposed into regular javascript objects and arrays return properties.reduce(function (obj, _ref2) { var name = _ref2.name, type = _ref2.type; return _objectSpread(_objectSpread({}, obj), {}, (0, _defineProperty2["default"])({}, name, domain.serialize(type, _this2._object[name]))); }, {}); } /** * Encode this object along with its type and domain as a full EIP712 * signature request, defining the types, domain, primaryType, and message * to be signed. The output is suitable for use with web3.eth.signTypedData * @returns {Object} the signature request encoding of this instance */ }, { key: "toSignatureRequest", value: function toSignatureRequest() { return { types: domain.toDomainDef(), domain: domain.toObject(), primaryType: this.name, message: this.toObject() }; } /** * @override * Return the EIP712 data encoding of this instance, padding each member * to 32 bytes and hashing the result. The ethereumjs-abi module provides * an encode() function which does most of the heavy lifting here, and the * structure of this function significantly inspired by the sample code * provided in the original EIP712 proposal. * @returns {String} the encoded data of this instance, ready for use with hashStruct */ }, { key: "encodeData", value: function encodeData() { // Build parallel lists of types and values, to be passed to abi.encode var types = ['bytes32']; var values = [_buffer.Buffer.from(this.constructor.typeHash(), 'hex')]; var _iterator2 = _createForOfIteratorHelper(this.constructor.properties), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _step2$value = _step2.value, type = _step2$value.type, name = _step2$value.name; if ((0, _primitives.isDynamicType)(type)) { // Dynamic types are hashed types.push('bytes32'); values.push(_buffer.Buffer.from(_ethers.ethers.utils.keccak256(_ethers.ethers.utils.toUtf8Bytes(this[name])).substring(2), 'hex')); } else if (type in domain.types) { // Structure Types are recursively encoded and hashed types.push('bytes32'); values.push(_buffer.Buffer.from(_ethers.ethers.utils.keccak256(this[name].encodeData()).substring(2), 'hex')); } else if ((0, _primitives.isArrayType)(type)) { // TODO: Figure out the spec for encoding array types throw new Error('[DEV] Array types not yet supported'); } else if ((0, _primitives.isAtomicType)(type)) { // Atomic types have their encoding defined by the solidity ABI types.push(type); values.push(this[name]); } else { throw new Error("Unknown type: ".concat(type)); } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } return _buffer.Buffer.from(_ethers.ethers.utils.defaultAbiCoder.encode(types, values).substring(2), 'hex'); } /** * ABI encode this type according to the EIP712 spec. The encoding returned * is compatible with solidity, and ready to be hashed and signed. * @returns {String} The abi encoding of this instance, in an appropriate format to be hashed and signed */ }, { key: "encode", value: function encode() { // \x19\x01 is the specified prefix for a typedData message return _buffer.Buffer.concat([_buffer.Buffer.from([0x19, 0x01]), domain.domainSeparator, this.hashStruct()]); } /** * Return the hash to be signed, simply the Keccak256 of the encoding * @returns {String} The hash, ready to be signed */ }, { key: "signHash", value: function signHash() { return _buffer.Buffer.from(_ethers.ethers.utils.keccak256(this.encode()).substring(2), 'hex'); } /** * Sign the fully encoded version of the current instance with a provided * signer. This is equivalent to the `web3.eth.signTypedData` function. * @param {Object} signer The signer function, which takes a buffer and return a signature * @returns {String} the signed, encoded piece of data */ }, { key: "sign", value: function sign(signer) { if (typeof signer !== 'function') { throw new Error('Must provide a signer function'); } return signer(this.signHash()); } /** * Verify a signature made by an address over this object * @param {Object} signature * @param {String} address * @returns {Boolean} whether the given signature is valid over this object */ }, { key: "verifySignature", value: function verifySignature(signature, address) { var hash = this.signHash(); return (0, _verify.verifyRawSignatureFromAddress)(hash, signature, address); } }], [{ key: "encodeDependentTypes", value: function encodeDependentTypes() { return this.dependencies.map(function (t) { return domain.types[t].encodeTypeFragment(); }); } }]); return StructureType; }(_AbstractType2["default"]); // Save the new StructureType to the domain (0, _defineProperty2["default"])(StructureType, "name", primaryType); (0, _defineProperty2["default"])(StructureType, "properties", properties); (0, _defineProperty2["default"])(StructureType, "dependencies", dependencies); domain.types[primaryType] = StructureType; return StructureType; } /** * Check the validity of a particular name/type pair within a given domain, * and return an object containing the name and type. * * @param {Object} item the item being validated, must contain `name` and `type` keys * @param {Object} domain the domain in which the item should be validated */ function validateTypeDefinition(_ref3, domain) { var name = _ref3.name, type = _ref3.type; if (!name || !type) { throw new Error('Invalid type definition: all entries must specify name and type'); } if ((0, _typeof2["default"])(type) === 'object') { // TODO: Allow recursive type defintions? throw new Error('Nested type definitions not supported'); } else if (!(0, _primitives.isPrimitiveType)(type) && !(type in domain.types)) { // Refuse undefined, non-primitive types throw new Error("Type ".concat(type, " is undefined in this domain")); } return { name: name, type: type }; } /** * Recursively search a list of properties to uncover a list of all dependent types * Each non-primitive type delegates to the dependencies of that type * * @param {Object[]} props The properties of a particular * @param {Object|Domain} domain The domain object in which the dependencies are being resolved * @param {String[]} found A list of structure type names found so far */ function findDependencies(props, domain) { var found = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; var _iterator3 = _createForOfIteratorHelper(props), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var type = _step3.value.type; if ((0, _primitives.isPrimitiveType)(type)) continue; // Merge the found array with new dependencies of found = found.concat([type].concat((0, _toConsumableArray2["default"])(findDependencies(domain.types[type].properties, domain, found))).filter(function (t) { return !found.includes(t); })); } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } return found; }