UNPKG

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
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