UNPKG

@symbioticfi/relay-stats-ts

Version:

TypeScript library for deriving validator sets from Symbiotic network contracts

151 lines 4.71 kB
import { keccak256 } from 'viem'; // Minimal BN254 (altbn128) helpers for G1 in affine coordinates. // This is a lightweight implementation sufficient for building extraData only. const FP_MODULUS = BigInt('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47'); // (p+1)/4 used for Tonelli-Shanks on BN254 (matches Go implementation) const SQRT_EXPONENT = BigInt('0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52'); function mod(a, m = FP_MODULUS) { const r = a % m; return r >= 0n ? r : r + m; } // reserved helpers (kept simple for clarity) function modMul(a, b) { return mod(a * b); } function modPow(base, exp) { let result = 1n; let b = mod(base); let e = exp; while (e > 0n) { if (e & 1n) result = modMul(result, b); b = modMul(b, b); e >>= 1n; } return result; } function modInv(a) { // a^(p-2) mod p for prime field return modPow(mod(a), FP_MODULUS - 2n); } function fromHex(hex) { const h = hex.startsWith('0x') ? hex.slice(2) : hex; if (h.length % 2 !== 0) throw new Error('Invalid hex'); return new Uint8Array(Buffer.from(h, 'hex')); } function toHex(bytes) { return ('0x' + Buffer.from(bytes).toString('hex')); } function bytesToBigIntBE(bytes) { // Preserve leading zeros by padding to the correct length const hex = Buffer.from(bytes).toString('hex'); return BigInt('0x' + hex); } function bigIntToBytes32BE(x) { const out = new Uint8Array(32); const hex = x.toString(16).padStart(64, '0'); out.set(Buffer.from(hex, 'hex')); return out; } function isInfinity(p) { return p === null; } // equality not required by current usage export function findYFromX(x) { // Go: Calculate beta = x^3 + 3 mod p const beta = mod(modPow(mod(x), 3n) + 3n); // Go: Calculate y = beta^((p+1)/4) mod p return modPow(beta, SQRT_EXPONENT); } export function compressG1FromXY(x, y) { const xMod = mod(x); const yMod = mod(y); const derivedY = findYFromX(xMod); const flag = yMod === derivedY ? 0n : 1n; const compressed = 2n * xMod + flag; return toHex(bigIntToBytes32BE(compressed)); } export function parseG1Uncompressed(raw) { const bytes = fromHex(raw); if (bytes.length < 64) throw new Error('Expected 64-byte uncompressed G1'); // Extract X and Y coordinates directly from hex string to preserve leading zeros const hex = raw.startsWith('0x') ? raw.slice(2) : raw; const xHex = hex.slice(0, 64); const yHex = hex.slice(64, 128); const x = BigInt('0x' + xHex); const y = BigInt('0x' + yHex.padStart(64, '0')); return { x: mod(x), y: mod(y) }; } // negation not required by current usage function pointDouble(p) { if (isInfinity(p)) return p; if (p.y === 0n) return null; const lambda = modMul(3n * modMul(p.x, p.x), modInv(2n * p.y)); const xr = mod(lambda * lambda - 2n * p.x); const yr = mod(lambda * (p.x - xr) - p.y); return { x: xr, y: yr }; } export function pointAdd(a, b) { if (isInfinity(a)) return b; if (isInfinity(b)) return a; if (a.x === b.x) { if (mod(a.y + b.y) === 0n) return null; return pointDouble(a); } const lambda = modMul(b.y - a.y, modInv(b.x - a.x)); const xr = mod(lambda * lambda - a.x - b.x); const yr = mod(lambda * (a.x - xr) - a.y); return { x: xr, y: yr }; } export function aggregateG1(keys) { let acc = null; for (const k of keys) { const p = parseKeyToPoint(k); acc = pointAdd(acc, p); } return acc; } export function compressAggregatedG1(keys) { const p = aggregateG1(keys); if (p === null) return ('0x' + '00'.repeat(32)); return compressG1FromXY(p.x, p.y); } export function compressRawG1(raw) { const p = parseKeyToPoint(raw); if (p === null) return ('0x' + '00'.repeat(32)); return compressG1FromXY(p.x, p.y); } export function keccak(bytes) { return keccak256(toHex(bytes)); } function parseCompressedToPoint(raw) { const bytes = fromHex(raw); if (bytes.length !== 32) throw new Error('Expected 32-byte compressed G1'); const v = bytesToBigIntBE(bytes); const x = v >> 1n; const flag = v & 1n; let y = findYFromX(x); if (flag === 1n) { y = mod(-y); } return { x: mod(x), y }; } export function parseKeyToPoint(raw) { const bytes = fromHex(raw); if (bytes.length >= 64) return parseG1Uncompressed(raw); if (bytes.length === 32) return parseCompressedToPoint(raw); throw new Error('Unsupported G1 key length'); } //# sourceMappingURL=bls_bn254.js.map