@ethersproject/hash
Version:
Hash utility functions for Ethereum.
501 lines • 22.1 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypedDataEncoder = void 0;
var address_1 = require("@ethersproject/address");
var bignumber_1 = require("@ethersproject/bignumber");
var bytes_1 = require("@ethersproject/bytes");
var keccak256_1 = require("@ethersproject/keccak256");
var properties_1 = require("@ethersproject/properties");
var logger_1 = require("@ethersproject/logger");
var _version_1 = require("./_version");
var logger = new logger_1.Logger(_version_1.version);
var id_1 = require("./id");
var padding = new Uint8Array(32);
padding.fill(0);
var NegativeOne = bignumber_1.BigNumber.from(-1);
var Zero = bignumber_1.BigNumber.from(0);
var One = bignumber_1.BigNumber.from(1);
var MaxUint256 = bignumber_1.BigNumber.from("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
function hexPadRight(value) {
var bytes = (0, bytes_1.arrayify)(value);
var padOffset = bytes.length % 32;
if (padOffset) {
return (0, bytes_1.hexConcat)([bytes, padding.slice(padOffset)]);
}
return (0, bytes_1.hexlify)(bytes);
}
var hexTrue = (0, bytes_1.hexZeroPad)(One.toHexString(), 32);
var hexFalse = (0, bytes_1.hexZeroPad)(Zero.toHexString(), 32);
var domainFieldTypes = {
name: "string",
version: "string",
chainId: "uint256",
verifyingContract: "address",
salt: "bytes32"
};
var domainFieldNames = [
"name", "version", "chainId", "verifyingContract", "salt"
];
function checkString(key) {
return function (value) {
if (typeof (value) !== "string") {
logger.throwArgumentError("invalid domain value for " + JSON.stringify(key), "domain." + key, value);
}
return value;
};
}
var domainChecks = {
name: checkString("name"),
version: checkString("version"),
chainId: function (value) {
try {
return bignumber_1.BigNumber.from(value).toString();
}
catch (error) { }
return logger.throwArgumentError("invalid domain value for \"chainId\"", "domain.chainId", value);
},
verifyingContract: function (value) {
try {
return (0, address_1.getAddress)(value).toLowerCase();
}
catch (error) { }
return logger.throwArgumentError("invalid domain value \"verifyingContract\"", "domain.verifyingContract", value);
},
salt: function (value) {
try {
var bytes = (0, bytes_1.arrayify)(value);
if (bytes.length !== 32) {
throw new Error("bad length");
}
return (0, bytes_1.hexlify)(bytes);
}
catch (error) { }
return logger.throwArgumentError("invalid domain value \"salt\"", "domain.salt", value);
}
};
function getBaseEncoder(type) {
// intXX and uintXX
{
var match = type.match(/^(u?)int(\d*)$/);
if (match) {
var signed = (match[1] === "");
var width = parseInt(match[2] || "256");
if (width % 8 !== 0 || width > 256 || (match[2] && match[2] !== String(width))) {
logger.throwArgumentError("invalid numeric width", "type", type);
}
var boundsUpper_1 = MaxUint256.mask(signed ? (width - 1) : width);
var boundsLower_1 = signed ? boundsUpper_1.add(One).mul(NegativeOne) : Zero;
return function (value) {
var v = bignumber_1.BigNumber.from(value);
if (v.lt(boundsLower_1) || v.gt(boundsUpper_1)) {
logger.throwArgumentError("value out-of-bounds for " + type, "value", value);
}
return (0, bytes_1.hexZeroPad)(v.toTwos(256).toHexString(), 32);
};
}
}
// bytesXX
{
var match = type.match(/^bytes(\d+)$/);
if (match) {
var width_1 = parseInt(match[1]);
if (width_1 === 0 || width_1 > 32 || match[1] !== String(width_1)) {
logger.throwArgumentError("invalid bytes width", "type", type);
}
return function (value) {
var bytes = (0, bytes_1.arrayify)(value);
if (bytes.length !== width_1) {
logger.throwArgumentError("invalid length for " + type, "value", value);
}
return hexPadRight(value);
};
}
}
switch (type) {
case "address": return function (value) {
return (0, bytes_1.hexZeroPad)((0, address_1.getAddress)(value), 32);
};
case "bool": return function (value) {
return ((!value) ? hexFalse : hexTrue);
};
case "bytes": return function (value) {
return (0, keccak256_1.keccak256)(value);
};
case "string": return function (value) {
return (0, id_1.id)(value);
};
}
return null;
}
function encodeType(name, fields) {
return name + "(" + fields.map(function (_a) {
var name = _a.name, type = _a.type;
return (type + " " + name);
}).join(",") + ")";
}
var TypedDataEncoder = /** @class */ (function () {
function TypedDataEncoder(types) {
(0, properties_1.defineReadOnly)(this, "types", Object.freeze((0, properties_1.deepCopy)(types)));
(0, properties_1.defineReadOnly)(this, "_encoderCache", {});
(0, properties_1.defineReadOnly)(this, "_types", {});
// Link struct types to their direct child structs
var links = {};
// Link structs to structs which contain them as a child
var parents = {};
// Link all subtypes within a given struct
var subtypes = {};
Object.keys(types).forEach(function (type) {
links[type] = {};
parents[type] = [];
subtypes[type] = {};
});
var _loop_1 = function (name_1) {
var uniqueNames = {};
types[name_1].forEach(function (field) {
// Check each field has a unique name
if (uniqueNames[field.name]) {
logger.throwArgumentError("duplicate variable name " + JSON.stringify(field.name) + " in " + JSON.stringify(name_1), "types", types);
}
uniqueNames[field.name] = true;
// Get the base type (drop any array specifiers)
var baseType = field.type.match(/^([^\x5b]*)(\x5b|$)/)[1];
if (baseType === name_1) {
logger.throwArgumentError("circular type reference to " + JSON.stringify(baseType), "types", types);
}
// Is this a base encoding type?
var encoder = getBaseEncoder(baseType);
if (encoder) {
return;
}
if (!parents[baseType]) {
logger.throwArgumentError("unknown type " + JSON.stringify(baseType), "types", types);
}
// Add linkage
parents[baseType].push(name_1);
links[name_1][baseType] = true;
});
};
for (var name_1 in types) {
_loop_1(name_1);
}
// Deduce the primary type
var primaryTypes = Object.keys(parents).filter(function (n) { return (parents[n].length === 0); });
if (primaryTypes.length === 0) {
logger.throwArgumentError("missing primary type", "types", types);
}
else if (primaryTypes.length > 1) {
logger.throwArgumentError("ambiguous primary types or unused types: " + primaryTypes.map(function (t) { return (JSON.stringify(t)); }).join(", "), "types", types);
}
(0, properties_1.defineReadOnly)(this, "primaryType", primaryTypes[0]);
// Check for circular type references
function checkCircular(type, found) {
if (found[type]) {
logger.throwArgumentError("circular type reference to " + JSON.stringify(type), "types", types);
}
found[type] = true;
Object.keys(links[type]).forEach(function (child) {
if (!parents[child]) {
return;
}
// Recursively check children
checkCircular(child, found);
// Mark all ancestors as having this decendant
Object.keys(found).forEach(function (subtype) {
subtypes[subtype][child] = true;
});
});
delete found[type];
}
checkCircular(this.primaryType, {});
// Compute each fully describe type
for (var name_2 in subtypes) {
var st = Object.keys(subtypes[name_2]);
st.sort();
this._types[name_2] = encodeType(name_2, types[name_2]) + st.map(function (t) { return encodeType(t, types[t]); }).join("");
}
}
TypedDataEncoder.prototype.getEncoder = function (type) {
var encoder = this._encoderCache[type];
if (!encoder) {
encoder = this._encoderCache[type] = this._getEncoder(type);
}
return encoder;
};
TypedDataEncoder.prototype._getEncoder = function (type) {
var _this = this;
// Basic encoder type (address, bool, uint256, etc)
{
var encoder = getBaseEncoder(type);
if (encoder) {
return encoder;
}
}
// Array
var match = type.match(/^(.*)(\x5b(\d*)\x5d)$/);
if (match) {
var subtype_1 = match[1];
var subEncoder_1 = this.getEncoder(subtype_1);
var length_1 = parseInt(match[3]);
return function (value) {
if (length_1 >= 0 && value.length !== length_1) {
logger.throwArgumentError("array length mismatch; expected length ${ arrayLength }", "value", value);
}
var result = value.map(subEncoder_1);
if (_this._types[subtype_1]) {
result = result.map(keccak256_1.keccak256);
}
return (0, keccak256_1.keccak256)((0, bytes_1.hexConcat)(result));
};
}
// Struct
var fields = this.types[type];
if (fields) {
var encodedType_1 = (0, id_1.id)(this._types[type]);
return function (value) {
var values = fields.map(function (_a) {
var name = _a.name, type = _a.type;
var result = _this.getEncoder(type)(value[name]);
if (_this._types[type]) {
return (0, keccak256_1.keccak256)(result);
}
return result;
});
values.unshift(encodedType_1);
return (0, bytes_1.hexConcat)(values);
};
}
return logger.throwArgumentError("unknown type: " + type, "type", type);
};
TypedDataEncoder.prototype.encodeType = function (name) {
var result = this._types[name];
if (!result) {
logger.throwArgumentError("unknown type: " + JSON.stringify(name), "name", name);
}
return result;
};
TypedDataEncoder.prototype.encodeData = function (type, value) {
return this.getEncoder(type)(value);
};
TypedDataEncoder.prototype.hashStruct = function (name, value) {
return (0, keccak256_1.keccak256)(this.encodeData(name, value));
};
TypedDataEncoder.prototype.encode = function (value) {
return this.encodeData(this.primaryType, value);
};
TypedDataEncoder.prototype.hash = function (value) {
return this.hashStruct(this.primaryType, value);
};
TypedDataEncoder.prototype._visit = function (type, value, callback) {
var _this = this;
// Basic encoder type (address, bool, uint256, etc)
{
var encoder = getBaseEncoder(type);
if (encoder) {
return callback(type, value);
}
}
// Array
var match = type.match(/^(.*)(\x5b(\d*)\x5d)$/);
if (match) {
var subtype_2 = match[1];
var length_2 = parseInt(match[3]);
if (length_2 >= 0 && value.length !== length_2) {
logger.throwArgumentError("array length mismatch; expected length ${ arrayLength }", "value", value);
}
return value.map(function (v) { return _this._visit(subtype_2, v, callback); });
}
// Struct
var fields = this.types[type];
if (fields) {
return fields.reduce(function (accum, _a) {
var name = _a.name, type = _a.type;
accum[name] = _this._visit(type, value[name], callback);
return accum;
}, {});
}
return logger.throwArgumentError("unknown type: " + type, "type", type);
};
TypedDataEncoder.prototype.visit = function (value, callback) {
return this._visit(this.primaryType, value, callback);
};
TypedDataEncoder.from = function (types) {
return new TypedDataEncoder(types);
};
TypedDataEncoder.getPrimaryType = function (types) {
return TypedDataEncoder.from(types).primaryType;
};
TypedDataEncoder.hashStruct = function (name, types, value) {
return TypedDataEncoder.from(types).hashStruct(name, value);
};
TypedDataEncoder.hashDomain = function (domain) {
var domainFields = [];
for (var name_3 in domain) {
var type = domainFieldTypes[name_3];
if (!type) {
logger.throwArgumentError("invalid typed-data domain key: " + JSON.stringify(name_3), "domain", domain);
}
domainFields.push({ name: name_3, type: type });
}
domainFields.sort(function (a, b) {
return domainFieldNames.indexOf(a.name) - domainFieldNames.indexOf(b.name);
});
return TypedDataEncoder.hashStruct("EIP712Domain", { EIP712Domain: domainFields }, domain);
};
TypedDataEncoder.encode = function (domain, types, value) {
return (0, bytes_1.hexConcat)([
"0x1901",
TypedDataEncoder.hashDomain(domain),
TypedDataEncoder.from(types).hash(value)
]);
};
TypedDataEncoder.hash = function (domain, types, value) {
return (0, keccak256_1.keccak256)(TypedDataEncoder.encode(domain, types, value));
};
// Replaces all address types with ENS names with their looked up address
TypedDataEncoder.resolveNames = function (domain, types, value, resolveName) {
return __awaiter(this, void 0, void 0, function () {
var ensCache, encoder, _a, _b, _i, name_4, _c, _d;
return __generator(this, function (_e) {
switch (_e.label) {
case 0:
// Make a copy to isolate it from the object passed in
domain = (0, properties_1.shallowCopy)(domain);
ensCache = {};
// Do we need to look up the domain's verifyingContract?
if (domain.verifyingContract && !(0, bytes_1.isHexString)(domain.verifyingContract, 20)) {
ensCache[domain.verifyingContract] = "0x";
}
encoder = TypedDataEncoder.from(types);
// Get a list of all the addresses
encoder.visit(value, function (type, value) {
if (type === "address" && !(0, bytes_1.isHexString)(value, 20)) {
ensCache[value] = "0x";
}
return value;
});
_a = [];
for (_b in ensCache)
_a.push(_b);
_i = 0;
_e.label = 1;
case 1:
if (!(_i < _a.length)) return [3 /*break*/, 4];
name_4 = _a[_i];
_c = ensCache;
_d = name_4;
return [4 /*yield*/, resolveName(name_4)];
case 2:
_c[_d] = _e.sent();
_e.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4:
// Replace the domain verifyingContract if needed
if (domain.verifyingContract && ensCache[domain.verifyingContract]) {
domain.verifyingContract = ensCache[domain.verifyingContract];
}
// Replace all ENS names with their address
value = encoder.visit(value, function (type, value) {
if (type === "address" && ensCache[value]) {
return ensCache[value];
}
return value;
});
return [2 /*return*/, { domain: domain, value: value }];
}
});
});
};
TypedDataEncoder.getPayload = function (domain, types, value) {
// Validate the domain fields
TypedDataEncoder.hashDomain(domain);
// Derive the EIP712Domain Struct reference type
var domainValues = {};
var domainTypes = [];
domainFieldNames.forEach(function (name) {
var value = domain[name];
if (value == null) {
return;
}
domainValues[name] = domainChecks[name](value);
domainTypes.push({ name: name, type: domainFieldTypes[name] });
});
var encoder = TypedDataEncoder.from(types);
var typesWithDomain = (0, properties_1.shallowCopy)(types);
if (typesWithDomain.EIP712Domain) {
logger.throwArgumentError("types must not contain EIP712Domain type", "types.EIP712Domain", types);
}
else {
typesWithDomain.EIP712Domain = domainTypes;
}
// Validate the data structures and types
encoder.encode(value);
return {
types: typesWithDomain,
domain: domainValues,
primaryType: encoder.primaryType,
message: encoder.visit(value, function (type, value) {
// bytes
if (type.match(/^bytes(\d*)/)) {
return (0, bytes_1.hexlify)((0, bytes_1.arrayify)(value));
}
// uint or int
if (type.match(/^u?int/)) {
return bignumber_1.BigNumber.from(value).toString();
}
switch (type) {
case "address":
return value.toLowerCase();
case "bool":
return !!value;
case "string":
if (typeof (value) !== "string") {
logger.throwArgumentError("invalid string", "value", value);
}
return value;
}
return logger.throwArgumentError("unsupported type", "type", type);
})
};
};
return TypedDataEncoder;
}());
exports.TypedDataEncoder = TypedDataEncoder;
//# sourceMappingURL=typed-data.js.map