eip-712
Version:
Tiny library with utility functions that can help with signing and verifying EIP-712 based messages
122 lines (101 loc) • 4.19 kB
JavaScript
import { getOptions } from './options';
import { ARRAY_REGEX, TYPE_REGEX } from './types';
import { keccak256, toBuffer, validateTypedData, encode } from './utils';
const EIP_191_PREFIX = Buffer.from('1901', 'hex');
export const getDependencies = (typedData, type, options, dependencies = []) => {
if (!validateTypedData(typedData, options)) {
throw new Error('Typed data does not match JSON schema');
}
const match = type.match(TYPE_REGEX);
const actualType = match[0];
if (dependencies.includes(actualType)) {
return dependencies;
}
if (!typedData.types[actualType]) {
return dependencies;
}
return [actualType, ...typedData.types[actualType].reduce((previous, type) => [...previous, ...getDependencies(typedData, type.type, options, previous).filter(dependency => !previous.includes(dependency))], [])];
};
export const encodeType = (typedData, type, options) => {
const [primary, ...dependencies] = getDependencies(typedData, type, options);
const types = [primary, ...dependencies.sort()];
return types.map(dependency => {
return `${dependency}(${typedData.types[dependency].map(type => `${type.type} ${type.name}`)})`;
}).join('');
};
export const getTypeHash = (typedData, type, options) => {
return keccak256(encodeType(typedData, type, options), 'utf8');
};
const encodeValue = (typedData, type, data, options) => {
const match = type.match(ARRAY_REGEX);
if (match) {
const arrayType = match[1];
const length = Number(match[2]) || undefined;
if (!Array.isArray(data)) {
throw new Error('Cannot encode data: value is not of array type');
}
if (length && data.length !== length) {
throw new Error(`Cannot encode data: expected length of ${length}, but got ${data.length}`);
}
const encodedData = data.map(item => encodeValue(typedData, arrayType, item, options));
const types = encodedData.map(item => item[0]);
const values = encodedData.map(item => item[1]);
return ['bytes32', keccak256(encode(types, values))];
}
if (typedData.types[type]) {
return ['bytes32', getStructHash(typedData, type, data, options)];
}
if (type === 'string') {
return ['bytes32', keccak256(data, 'utf8')];
}
if (type === 'bytes') {
return ['bytes32', keccak256(Buffer.isBuffer(data) ? data : toBuffer(data), 'hex')];
}
return [type, data];
};
export const encodeData = (typedData, type, data, options) => {
const [types, values] = typedData.types[type].reduce(([types, values], field) => {
if (data[field.name] === undefined || data[field.name] === null) {
throw new Error(`Cannot encode data: missing data for '${field.name}'`);
}
const value = data[field.name];
const [type, encodedValue] = encodeValue(typedData, field.type, value, options);
return [[...types, type], [...values, encodedValue]];
}, [['bytes32'], [getTypeHash(typedData, type, options)]]);
return encode(types, values);
};
export const getStructHash = (typedData, type, data, options) => {
return keccak256(encodeData(typedData, type, data, options));
};
export const getMessage = (typedData, hash, options) => {
const {
domain
} = getOptions(options);
const message = Buffer.concat([EIP_191_PREFIX, getStructHash(typedData, domain, typedData.domain, options), getStructHash(typedData, typedData.primaryType, typedData.message, options)]);
if (hash) {
return keccak256(message);
}
return message;
};
export const asArray = (typedData, type = typedData.primaryType, data = typedData.message, options) => {
if (!validateTypedData(typedData, options)) {
throw new Error('Typed data does not match JSON schema');
}
if (!typedData.types[type]) {
throw new Error('Cannot get data as array: type does not exist');
}
return typedData.types[type].reduce((array, {
name,
type
}) => {
if (typedData.types[type]) {
if (!data[name]) {
throw new Error(`Cannot get data as array: missing data for '${name}'`);
}
return [...array, asArray(typedData, type, data[name], options)];
}
const value = data[name];
return [...array, value];
}, []);
};
//# sourceMappingURL=eip-712.js.map