react-native-eth-typed-data
Version:
A library to simplifiy interacting with and signing EIP712 typed data
387 lines (321 loc) • 16.7 kB
JavaScript
;
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;
}