@confluxfans/cip-23
Version:
Tiny library with utility functions that can help with signing and verifying CIP-23 based messages
118 lines (97 loc) • 3.92 kB
JavaScript
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, dependencies = []) => {
if (!validateTypedData(typedData)) {
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, previous).filter(dependency => !previous.includes(dependency))], [])];
};
export const encodeType = (typedData, type) => {
const [primary, ...dependencies] = getDependencies(typedData, type);
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) => {
return keccak256(encodeType(typedData, type), 'utf8');
};
const encodeValue = (typedData, type, data) => {
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));
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)];
}
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) => {
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);
return [[...types, type], [...values, encodedValue]];
}, [['bytes32'], [getTypeHash(typedData, type)]]);
return encode(types, values);
};
export const getStructHash = (typedData, type, data) => {
return keccak256(encodeData(typedData, type, data));
};
export const getMessage = (typedData, hash) => {
const message = Buffer.concat([EIP_191_PREFIX, getStructHash(typedData, 'CIP23Domain', typedData.domain), getStructHash(typedData, typedData.primaryType, typedData.message)]);
if (hash) {
return keccak256(message);
}
return message;
};
export const asArray = (typedData, type = typedData.primaryType, data = typedData.message) => {
if (!validateTypedData(typedData)) {
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])];
}
const value = data[name];
return [...array, value];
}, []);
};
//# sourceMappingURL=cip-23.js.map