UNPKG

@symbioticfi/relay-stats-ts

Version:

TypeScript library for deriving validator sets from Symbiotic network contracts

181 lines 7 kB
import { encodeAbiParameters, keccak256 } from 'viem'; import { KeyType, getKeyType } from './types.js'; import { compressAggregatedG1, compressRawG1, parseKeyToPoint } from './bls_bn254.js'; import { FIELD_MODULUS, mimcBn254Hash } from './mimc_bn254.js'; import { hexToBytes } from './encoding.js'; import { EXTRA_NAME, EXTRA_PREFIX } from './constants.js'; import { bigintToBytes32, bytesToBigint, bytesToLimbs, sortHexAsc } from './utils.js'; const keccakName = (name) => { const hex = `0x${Buffer.from(name, 'utf8').toString('hex')}`; return keccak256(hex); }; const computeExtraDataKey = (verificationType, name) => { const encoded = encodeAbiParameters([ { name: 'vt', type: 'uint32' }, { name: 'name', type: 'bytes32' }, ], [verificationType, keccakName(name)]); return keccak256(encoded); }; const computeExtraDataKeyTagged = (verificationType, tag, name) => { const prefix = keccakName(EXTRA_PREFIX.TAG); const nameHash = keccakName(name); const encoded = encodeAbiParameters([ { name: 'vt', type: 'uint32' }, { name: 'prefix', type: 'bytes32' }, { name: 'tag', type: 'uint8' }, { name: 'name', type: 'bytes32' }, ], [verificationType, prefix, tag, nameHash]); return keccak256(encoded); }; const modField = (value) => { const remainder = value % FIELD_MODULUS; return remainder >= 0n ? remainder : remainder + FIELD_MODULUS; }; const filterBlsBn254Tags = (keyTags) => { const tags = []; for (const tag of keyTags) { if (getKeyType(tag) === KeyType.KeyTypeBlsBn254) { tags.push(tag); } } return tags; }; const collectValidatorsForSimple = (validatorSet, tag) => { const tuples = []; const activeKeys = []; for (const validator of validatorSet.validators) { if (!validator.isActive) continue; let payload = null; for (const key of validator.keys) { if (key.tag === tag) { payload = key.payload; break; } } if (!payload) { throw new Error(`Failed to find key by keyTag ${tag} for validator ${validator.operator}`); } tuples.push({ keySerialized: compressRawG1(payload), votingPower: validator.votingPower, }); activeKeys.push(payload); } tuples.sort((left, right) => left.keySerialized < right.keySerialized ? -1 : left.keySerialized > right.keySerialized ? 1 : 0); return { validatorTuples: tuples, aggregatedKeyCompressed: compressAggregatedG1(activeKeys), }; }; const collectValidatorsForZk = (validatorSet, tag) => { const tuples = []; for (const validator of validatorSet.validators) { if (!validator.isActive) continue; let payload = null; for (const key of validator.keys) { if (key.tag === tag) { payload = key.payload; break; } } if (!payload) { throw new Error(`Failed to find key by keyTag ${tag} for validator ${validator.operator}`); } const point = parseKeyToPoint(payload); if (point === null) { throw new Error(`Validator ${validator.operator} key resolves to point at infinity`); } tuples.push({ keySerialized: compressRawG1(payload), votingPower: validator.votingPower, x: modField(point.x), y: modField(point.y), }); } tuples.sort((left, right) => { if (left.x === right.x && left.y === right.y) return 0; const leftBeforeRight = left.x < right.x || left.y < right.y; return leftBeforeRight ? -1 : 1; }); return tuples; }; const keccakValidatorsData = (tuples) => { const encoded = encodeAbiParameters([ { name: 'validators', type: 'tuple[]', components: [ { name: 'keySerialized', type: 'bytes' }, { name: 'VotingPower', type: 'uint256' }, ], }, ], [ tuples.map((tuple) => ({ keySerialized: tuple.keySerialized, VotingPower: tuple.votingPower, })), ]); const encodedBytes = hexToBytes(encoded); const tail = encodedBytes.slice(32); return keccak256(tail); }; const mimcHashValidators = async (tuples) => { if (tuples.length === 0) { return `0x${'0'.repeat(64)}`; } let state = 0n; for (const validator of tuples) { if (validator.x === 0n && validator.y === 0n) break; const xLimbs = bytesToLimbs(bigintToBytes32(validator.x), 8); const yLimbs = bytesToLimbs(bigintToBytes32(validator.y), 8); const votingPowerField = bytesToBigint(bigintToBytes32(validator.votingPower)); for (const limb of [...xLimbs, ...yLimbs]) { state = modField(mimcBn254Hash(state, limb)); } state = modField(mimcBn254Hash(state, votingPowerField)); } const finalHash = modField(state); const bytes = bigintToBytes32(finalHash); return `0x${Buffer.from(bytes).toString('hex')}`; }; export const buildSimpleExtraData = (validatorSet, keyTags) => { const entries = []; const filteredTags = filterBlsBn254Tags(keyTags); for (const tag of filteredTags) { const { validatorTuples, aggregatedKeyCompressed } = collectValidatorsForSimple(validatorSet, tag); if (validatorTuples.length === 0) continue; const validatorsKeccak = keccakValidatorsData(validatorTuples); const validatorKey = computeExtraDataKeyTagged(1, tag, EXTRA_NAME.SIMPLE_VALIDATORS_KECCAK); entries.push({ key: validatorKey, value: validatorsKeccak }); const aggregatedKey = computeExtraDataKeyTagged(1, tag, EXTRA_NAME.SIMPLE_AGG_G1); entries.push({ key: aggregatedKey, value: aggregatedKeyCompressed }); } return sortHexAsc(entries); }; export const buildZkExtraData = async (validatorSet, keyTags) => { const entries = []; const totalActive = validatorSet.validators.filter((validator) => validator.isActive).length; const totalActiveKey = computeExtraDataKey(0, EXTRA_NAME.ZK_TOTAL_ACTIVE); const totalActiveBytes = `0x${totalActive.toString(16).padStart(64, '0')}`; entries.push({ key: totalActiveKey, value: totalActiveBytes }); const filteredTags = filterBlsBn254Tags(keyTags); for (const tag of filteredTags) { const tuples = collectValidatorsForZk(validatorSet, tag); if (tuples.length === 0) continue; const mimcAccumulator = await mimcHashValidators(tuples); const validatorsKey = computeExtraDataKeyTagged(0, tag, EXTRA_NAME.ZK_VALIDATORS_MIMC); entries.push({ key: validatorsKey, value: mimcAccumulator }); } return sortHexAsc(entries); }; //# sourceMappingURL=extra_data.js.map